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.
- package/LICENSE.md +24 -0
- package/README.md +55 -2
- package/bin/create-snapify-app.mjs +174 -0
- package/lib/args.mjs +59 -0
- package/lib/copy.mjs +44 -0
- package/lib/env.mjs +84 -0
- package/lib/prompt.mjs +63 -0
- package/package.json +33 -4
- package/template/.env.example +79 -0
- package/template/.github/workflows/ci.yml +23 -0
- package/template/.github/workflows/deploy.yml +26 -0
- package/template/.github/workflows/ping.yml +8 -0
- package/template/.github/workflows/release.yml +67 -0
- package/template/.snapify-install.json +5 -0
- package/template/CHANGELOG.md +28 -0
- package/template/LICENSE +14 -0
- package/template/README.md +287 -0
- package/template/administrator.html +23 -0
- package/template/apps/client/src/App.tsx +765 -0
- package/template/apps/client/src/auth.css +400 -0
- package/template/apps/client/src/lib/api.ts +22 -0
- package/template/apps/client/src/main.tsx +20 -0
- package/template/apps/community/src/App.tsx +592 -0
- package/template/apps/community/src/main.tsx +20 -0
- package/template/apps/community/src/styles.css +24 -0
- package/template/apps/developers/src/App.tsx +643 -0
- package/template/apps/developers/src/main.tsx +17 -0
- package/template/apps/developers/src/pages.tsx +1587 -0
- package/template/apps/developers/src/styles.css +98 -0
- package/template/apps/developers/src/ui.tsx +179 -0
- package/template/apps/landing/src/App.tsx +598 -0
- package/template/apps/landing/src/LegalPages.tsx +158 -0
- package/template/apps/landing/src/OrderPage.tsx +381 -0
- package/template/apps/landing/src/SeoPages.tsx +923 -0
- package/template/apps/landing/src/copy.ts +195 -0
- package/template/apps/landing/src/main.tsx +20 -0
- package/template/apps/landing/src/styles.css +803 -0
- package/template/apps/landing/src/types.ts +19 -0
- package/template/apps/marketplace/src/App.tsx +1229 -0
- package/template/apps/marketplace/src/developer/DeveloperApp.tsx +1266 -0
- package/template/apps/marketplace/src/main.tsx +20 -0
- package/template/apps/marketplace/src/styles.css +1001 -0
- package/template/apps/shared/AuthContext.tsx +65 -0
- package/template/apps/shared/Logo.tsx +33 -0
- package/template/apps/shared/api.ts +47 -0
- package/template/apps/shared/ui.tsx +128 -0
- package/template/apps/superadmin/src/App.tsx +1264 -0
- package/template/apps/superadmin/src/lib/api.ts +20 -0
- package/template/apps/superadmin/src/lib/hooks.ts +20 -0
- package/template/apps/superadmin/src/main.tsx +16 -0
- package/template/apps/superadmin/src/pages/ClientsPage.tsx +185 -0
- package/template/apps/superadmin/src/pages/InvoicesPage.tsx +157 -0
- package/template/apps/superadmin/src/pages/NewsletterPage.tsx +212 -0
- package/template/apps/superadmin/src/pages/OrdersPage.tsx +195 -0
- package/template/apps/superadmin/src/pages/PaymentsPage.tsx +150 -0
- package/template/apps/superadmin/src/pages/PayoutsPage.tsx +133 -0
- package/template/apps/superadmin/src/pages/PublishersPage.tsx +151 -0
- package/template/apps/superadmin/src/pages/SeoControlPage.tsx +692 -0
- package/template/apps/superadmin/src/pages/SettingsPage.tsx +199 -0
- package/template/apps/superadmin/src/pages/SystemPage.tsx +109 -0
- package/template/apps/superadmin/src/pages/TicketsPage.tsx +201 -0
- package/template/apps/superadmin/src/pages/UsersPage.tsx +167 -0
- package/template/client.html +21 -0
- package/template/community/hamdiayari/akhbar/README.md +23 -0
- package/template/community/hamdiayari/akhbar/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar/snapify.theme.json +76 -0
- package/template/community/hamdiayari/akhbar/src/Brand.tsx +26 -0
- package/template/community/hamdiayari/akhbar/src/Footer.tsx +182 -0
- package/template/community/hamdiayari/akhbar/src/Header.tsx +191 -0
- package/template/community/hamdiayari/akhbar/src/Layout.tsx +49 -0
- package/template/community/hamdiayari/akhbar/src/Social.tsx +33 -0
- package/template/community/hamdiayari/akhbar/src/Ticker.tsx +86 -0
- package/template/community/hamdiayari/akhbar/src/Views.tsx +285 -0
- package/template/community/hamdiayari/akhbar/src/blocks/CollectionBlocks.tsx +672 -0
- package/template/community/hamdiayari/akhbar/src/blocks/ContentBlocks.tsx +305 -0
- package/template/community/hamdiayari/akhbar/src/blocks/ConversionBlocks.tsx +569 -0
- package/template/community/hamdiayari/akhbar/src/blocks/HeaderBlocks.tsx +287 -0
- package/template/community/hamdiayari/akhbar/src/index.tsx +51 -0
- package/template/community/hamdiayari/akhbar/src/kit.tsx +357 -0
- package/template/community/hamdiayari/akhbar/src/site.ts +96 -0
- package/template/community/hamdiayari/akhbar/src/styles.css +566 -0
- package/template/community/hamdiayari/akhbar-actualites/README.md +28 -0
- package/template/community/hamdiayari/akhbar-actualites/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar-actualites/snapify.module.json +107 -0
- package/template/community/hamdiayari/akhbar-actualites/src/admin/AudiencePage.tsx +185 -0
- package/template/community/hamdiayari/akhbar-actualites/src/admin/index.tsx +11 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/Archive.tsx +142 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/cards.tsx +152 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/index.tsx +71 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/posts.ts +103 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/styles.css +568 -0
- package/template/community/hamdiayari/akhbar-actualites/src/public/widgets.tsx +226 -0
- package/template/community/hamdiayari/akhbar-actualites/src/server.ts +119 -0
- package/template/community/hamdiayari/akhbar-actualites/src/types.ts +25 -0
- package/template/community/hamdiayari/akhbar-urgent/README.md +36 -0
- package/template/community/hamdiayari/akhbar-urgent/screenshot.jpg +0 -0
- package/template/community/hamdiayari/akhbar-urgent/snapify.module.json +64 -0
- package/template/community/hamdiayari/akhbar-urgent/src/admin/FlashesPage.tsx +229 -0
- package/template/community/hamdiayari/akhbar-urgent/src/admin/index.tsx +11 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/Ticker.tsx +81 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/index.tsx +63 -0
- package/template/community/hamdiayari/akhbar-urgent/src/public/styles.css +142 -0
- package/template/community/hamdiayari/akhbar-urgent/src/server.ts +101 -0
- package/template/community/hamdiayari/akhbar-urgent/src/types.ts +35 -0
- package/template/community.html +21 -0
- package/template/developers.html +24 -0
- package/template/docker-compose.db.yml +24 -0
- package/template/docs/BACKOFFICE_SNAPIFY.md +130 -0
- package/template/docs/CI_CD.md +66 -0
- package/template/docs/MODULES.md +346 -0
- package/template/docs/PLATFORM_DEPLOY.md +54 -0
- package/template/docs/logo.svg +10 -0
- package/template/drizzle/0000_init.sql +147 -0
- package/template/drizzle/0001_page_project.sql +5 -0
- package/template/drizzle/0002_modules.sql +23 -0
- package/template/drizzle/0003_themes.sql +4 -0
- package/template/drizzle/0004_security.sql +82 -0
- package/template/drizzle/0005_module_records.sql +16 -0
- package/template/drizzle/0006_avatars.sql +4 -0
- package/template/drizzle/0007_platform.sql +264 -0
- package/template/drizzle/0008_mp_validation.sql +3 -0
- package/template/drizzle/0009_saas_ops.sql +77 -0
- package/template/drizzle/0010_cms_chrome.sql +13 -0
- package/template/drizzle/0011_client_portal.sql +26 -0
- package/template/drizzle/meta/0000_snapshot.json +998 -0
- package/template/drizzle/meta/0001_snapshot.json +1004 -0
- package/template/drizzle/meta/_journal.json +90 -0
- package/template/drizzle.config.ts +16 -0
- package/template/ecosystem.snapify.cjs +24 -0
- package/template/for.html +1 -0
- package/template/index.html +29 -0
- package/template/landing.html +37 -0
- package/template/marketplace/catalog.json +373 -0
- package/template/marketplace/gallery/hamdiayari.preveris/01-home.jpg +0 -0
- package/template/marketplace/gallery/hamdiayari.recaptcha/01-reglages.jpg +0 -0
- package/template/marketplace/gallery/hamdiayari.recaptcha/02-logo.jpg +0 -0
- package/template/marketplace/gallery/novalabs.ai-articles/01-generer.jpg +0 -0
- package/template/marketplace/gallery/novalabs.ai-articles/02-fournisseur-ia.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/01-agenda-site.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/02-evenements-admin.jpg +0 -0
- package/template/marketplace/gallery/novalabs.calendar/03-reglages.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.preveris-1.0.6.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.recaptcha-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/hamdiayari.wp-seo-migrator-1.1.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.ai-articles-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.ai-chat-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.calendar-1.0.1.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.cart-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.checkout-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.customers-1.1.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.dogga-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.mahdia-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.shop-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.sidibou-1.0.0.jpg +0 -0
- package/template/marketplace/screenshots/novalabs.whatsapp-1.0.1.jpg +0 -0
- package/template/marketplace.html +21 -0
- package/template/metadata.json +7 -0
- package/template/modules/ai-articles/screenshot.jpg +0 -0
- package/template/modules/ai-articles/snapify.module.json +74 -0
- package/template/modules/ai-articles/src/admin/ArticlesPage.tsx +162 -0
- package/template/modules/ai-articles/src/admin/GeneratePage.tsx +118 -0
- package/template/modules/ai-articles/src/admin/index.tsx +12 -0
- package/template/modules/ai-articles/src/public/index.tsx +119 -0
- package/template/modules/ai-articles/src/server.ts +287 -0
- package/template/modules/ai-articles/src/types.ts +30 -0
- package/template/modules/ai-chat/screenshot.jpg +0 -0
- package/template/modules/ai-chat/snapify.module.json +217 -0
- package/template/modules/ai-chat/src/public.tsx +198 -0
- package/template/modules/ai-chat/src/server.ts +142 -0
- package/template/modules/ai-theme-studio/README.md +15 -0
- package/template/modules/ai-theme-studio/screenshot.jpg +0 -0
- package/template/modules/ai-theme-studio/snapify.module.json +175 -0
- package/template/modules/ai-theme-studio/src/admin/HistoryPage.tsx +91 -0
- package/template/modules/ai-theme-studio/src/admin/StudioPage.tsx +414 -0
- package/template/modules/ai-theme-studio/src/admin/index.tsx +11 -0
- package/template/modules/ai-theme-studio/src/lib/images.ts +240 -0
- package/template/modules/ai-theme-studio/src/lib/llm.ts +123 -0
- package/template/modules/ai-theme-studio/src/lib/normalize.ts +714 -0
- package/template/modules/ai-theme-studio/src/lib/prompts.ts +198 -0
- package/template/modules/ai-theme-studio/src/server.ts +137 -0
- package/template/modules/ai-theme-studio/src/types.ts +112 -0
- package/template/modules/calendar/screenshot.jpg +0 -0
- package/template/modules/calendar/snapify.module.json +45 -0
- package/template/modules/calendar/src/admin/EventsPage.tsx +217 -0
- package/template/modules/calendar/src/admin/index.tsx +11 -0
- package/template/modules/calendar/src/public/index.tsx +231 -0
- package/template/modules/calendar/src/server.ts +143 -0
- package/template/modules/calendar/src/types.ts +25 -0
- package/template/modules/cart/screenshot.jpg +0 -0
- package/template/modules/cart/snapify.module.json +90 -0
- package/template/modules/cart/src/admin/index.tsx +265 -0
- package/template/modules/cart/src/public/CartPage.tsx +198 -0
- package/template/modules/cart/src/public/index.tsx +261 -0
- package/template/modules/cart/src/public/useQuote.ts +67 -0
- package/template/modules/cart/src/server.ts +148 -0
- package/template/modules/cart/src/types.ts +31 -0
- package/template/modules/checkout/screenshot.jpg +0 -0
- package/template/modules/checkout/snapify.module.json +264 -0
- package/template/modules/checkout/src/admin/OrderDetailPage.tsx +304 -0
- package/template/modules/checkout/src/admin/badges.tsx +28 -0
- package/template/modules/checkout/src/admin/index.tsx +236 -0
- package/template/modules/checkout/src/public/CheckoutPage.tsx +461 -0
- package/template/modules/checkout/src/public/OrderPages.tsx +153 -0
- package/template/modules/checkout/src/public/index.tsx +23 -0
- package/template/modules/checkout/src/server.ts +383 -0
- package/template/modules/checkout/src/types.ts +70 -0
- package/template/modules/customers/screenshot.jpg +0 -0
- package/template/modules/customers/snapify.module.json +115 -0
- package/template/modules/customers/src/admin/CustomerDetailPage.tsx +274 -0
- package/template/modules/customers/src/admin/index.tsx +194 -0
- package/template/modules/customers/src/public/AccountPages.tsx +470 -0
- package/template/modules/customers/src/public/AddressForm.tsx +115 -0
- package/template/modules/customers/src/public/AuthPages.tsx +349 -0
- package/template/modules/customers/src/public/Avatar.tsx +29 -0
- package/template/modules/customers/src/public/ProfileSection.tsx +291 -0
- package/template/modules/customers/src/public/index.tsx +137 -0
- package/template/modules/customers/src/public/session.ts +51 -0
- package/template/modules/customers/src/server.ts +421 -0
- package/template/modules/customers/src/types.ts +53 -0
- package/template/modules/recaptcha/logo.jpg +0 -0
- package/template/modules/recaptcha/screenshot.jpg +0 -0
- package/template/modules/recaptcha/snapify.module.json +99 -0
- package/template/modules/recaptcha/src/public/index.tsx +94 -0
- package/template/modules/recaptcha/src/server.ts +65 -0
- package/template/modules/recaptcha/src/types.ts +18 -0
- package/template/modules/shop/screenshot.jpg +0 -0
- package/template/modules/shop/snapify.module.json +340 -0
- package/template/modules/shop/src/admin/CategoriesPage.tsx +224 -0
- package/template/modules/shop/src/admin/ProductEditor.tsx +313 -0
- package/template/modules/shop/src/admin/ProductsPage.tsx +307 -0
- package/template/modules/shop/src/admin/index.tsx +14 -0
- package/template/modules/shop/src/public/Catalog.tsx +244 -0
- package/template/modules/shop/src/public/NotFound.tsx +22 -0
- package/template/modules/shop/src/public/ProductPage.tsx +195 -0
- package/template/modules/shop/src/public/index.tsx +134 -0
- package/template/modules/shop/src/public/useFetch.ts +26 -0
- package/template/modules/shop/src/server.ts +493 -0
- package/template/modules/shop/src/shared.ts +41 -0
- package/template/modules/shop/src/types.ts +60 -0
- package/template/modules/whatsapp/screenshot.jpg +0 -0
- package/template/modules/whatsapp/snapify.module.json +121 -0
- package/template/modules/whatsapp/src/public.tsx +119 -0
- package/template/modules/wp-seo-migrator/ARCHITECTURE.md +113 -0
- package/template/modules/wp-seo-migrator/README.md +21 -0
- package/template/modules/wp-seo-migrator/fixtures/aioseo-sample.csv +3 -0
- package/template/modules/wp-seo-migrator/fixtures/sample-exporter.json +39 -0
- package/template/modules/wp-seo-migrator/fixtures/sample-page.html +12 -0
- package/template/modules/wp-seo-migrator/fixtures/sample.htaccess +3 -0
- package/template/modules/wp-seo-migrator/screenshot.jpg +0 -0
- package/template/modules/wp-seo-migrator/snapify.module.json +87 -0
- package/template/modules/wp-seo-migrator/src/admin/HistoryPage.tsx +64 -0
- package/template/modules/wp-seo-migrator/src/admin/WizardPage.tsx +788 -0
- package/template/modules/wp-seo-migrator/src/admin/i18n.ts +156 -0
- package/template/modules/wp-seo-migrator/src/admin/index.tsx +11 -0
- package/template/modules/wp-seo-migrator/src/connectors/CUSTOM_CONNECTOR.md +31 -0
- package/template/modules/wp-seo-migrator/src/connectors/csvSeoFactory.ts +106 -0
- package/template/modules/wp-seo-migrator/src/connectors/exporter.ts +96 -0
- package/template/modules/wp-seo-migrator/src/connectors/gscCsv.ts +53 -0
- package/template/modules/wp-seo-migrator/src/connectors/htaccess.ts +65 -0
- package/template/modules/wp-seo-migrator/src/connectors/htmlCrawler.ts +90 -0
- package/template/modules/wp-seo-migrator/src/connectors/index.ts +113 -0
- package/template/modules/wp-seo-migrator/src/connectors/pluginCsv.ts +77 -0
- package/template/modules/wp-seo-migrator/src/connectors/rankMathCsv.ts +77 -0
- package/template/modules/wp-seo-migrator/src/connectors/redirection.ts +62 -0
- package/template/modules/wp-seo-migrator/src/connectors/sitemap.ts +44 -0
- package/template/modules/wp-seo-migrator/src/connectors/utils.ts +111 -0
- package/template/modules/wp-seo-migrator/src/connectors/wxr.ts +103 -0
- package/template/modules/wp-seo-migrator/src/connectors/yoastCsv.ts +56 -0
- package/template/modules/wp-seo-migrator/src/pipeline/analyze.ts +158 -0
- package/template/modules/wp-seo-migrator/src/pipeline/htmlParse.ts +58 -0
- package/template/modules/wp-seo-migrator/src/pipeline/remoteAuto.ts +328 -0
- package/template/modules/wp-seo-migrator/src/server.ts +492 -0
- package/template/modules/wp-seo-migrator/src/types.ts +151 -0
- package/template/modules/wp-seo-migrator/tests/connectors-extra.test.ts +39 -0
- package/template/modules/wp-seo-migrator/tests/merge.test.ts +25 -0
- package/template/package-lock.json +6016 -0
- package/template/package.json +74 -0
- package/template/public/images/photos/caravane-chameaux-desert.webp +0 -0
- package/template/public/images/photos/hammamet-medina-plage.jpg +0 -0
- package/template/public/images/photos/maison-campagne-collines.webp +0 -0
- package/template/public/images/photos/maisons-troglodytes-desert.jpg +0 -0
- package/template/public/images/photos/panorama-ville.jpg +0 -0
- package/template/public/images/photos/preveris-accessibilite-erp.jpg +0 -0
- package/template/public/images/photos/preveris-collectivites.jpg +0 -0
- package/template/public/images/photos/preveris-etudes-plans.jpg +0 -0
- package/template/public/images/photos/preveris-hero-erp.jpg +0 -0
- package/template/public/images/photos/preveris-ssi-incendie.jpg +0 -0
- package/template/public/images/photos/preveris-suivi-hotel.jpg +0 -0
- package/template/public/images/photos/ruelle-bleue-medina.jpg +0 -0
- package/template/public/images/photos/sidi-bou-said-port.webp +0 -0
- package/template/public/images/photos/terrasse-blanche-mer-voilier.jpg +0 -0
- package/template/public/landing/hero-ai-cms.jpg +0 -0
- package/template/public/landing/shot-dogga.jpg +0 -0
- package/template/public/landing/shot-mahdia.jpg +0 -0
- package/template/public/landing/shot-sidibou.jpg +0 -0
- package/template/public/llms.txt +35 -0
- package/template/public/logo.svg +10 -0
- package/template/scripts/app-pack.ts +155 -0
- package/template/scripts/app-publish.ts +100 -0
- package/template/scripts/apply-ecommerce-home.ts +196 -0
- package/template/scripts/auto-deploy-poll.sh +13 -0
- package/template/scripts/db-backup.sh +16 -0
- package/template/scripts/db-restore.sh +11 -0
- package/template/scripts/deploy.sh +34 -0
- package/template/scripts/github-webhook-deploy.sh +7 -0
- package/template/scripts/install-new-modules.ts +96 -0
- package/template/scripts/refresh-demo-baseline.ts +9 -0
- package/template/scripts/reinstall-marketplace-packs.ts +51 -0
- package/template/scripts/restore-classic-home.ts +13 -0
- package/template/scripts/sdk-pack.ts +289 -0
- package/template/scripts/sdk-publish.ts +75 -0
- package/template/scripts/seed-demo-products.ts +120 -0
- package/template/scripts/seed-theme-content-packs.ts +42 -0
- package/template/scripts/snapify-module.ts +207 -0
- package/template/scripts/test-theme-switch.ts +24 -0
- package/template/sdk/admin.css +18 -0
- package/template/sdk/admin.ts +95 -0
- package/template/sdk/build.ts +249 -0
- package/template/sdk/commerce.ts +512 -0
- package/template/sdk/content.ts +76 -0
- package/template/sdk/icons.ts +101 -0
- package/template/sdk/image.ts +60 -0
- package/template/sdk/index.ts +500 -0
- package/template/sdk/package.ts +298 -0
- package/template/sdk/recaptcha.tsx +122 -0
- package/template/sdk/settings.ts +84 -0
- package/template/sdk/theme.css +29 -0
- package/template/sdk/theme.ts +100 -0
- package/template/sdk/ui/commerce.tsx +385 -0
- package/template/sdk/ui/orders.tsx +136 -0
- package/template/sdk-kit/CHANGELOG.md +17 -0
- package/template/sdk-kit/LICENSE.md +47 -0
- package/template/sdk-kit/README.md +81 -0
- package/template/sdk-kit/cli.ts +339 -0
- package/template/sdk-kit/create-snapify/README.md +28 -0
- package/template/sdk-kit/create-snapify/create-snapify.mjs +11 -0
- package/template/sdk-kit/create-snapify/package.template.json +16 -0
- package/template/sdk-kit/package.template.json +95 -0
- package/template/sdk-kit/templates/common/README.md.tpl +27 -0
- package/template/sdk-kit/templates/common/_gitignore +3 -0
- package/template/sdk-kit/templates/common/package.json.tpl +19 -0
- package/template/sdk-kit/templates/common/src/snapify-env.d.ts +2 -0
- package/template/sdk-kit/templates/common/tsconfig.json +15 -0
- package/template/sdk-kit/templates/module/snapify.module.json.tpl +33 -0
- package/template/sdk-kit/templates/module/src/admin.tsx +53 -0
- package/template/sdk-kit/templates/module/src/public.tsx +49 -0
- package/template/sdk-kit/templates/module/src/server.ts +30 -0
- package/template/sdk-kit/templates/module/src/types.ts +19 -0
- package/template/sdk-kit/templates/theme/snapify.theme.json.tpl +22 -0
- package/template/sdk-kit/templates/theme/src/Footer.tsx +40 -0
- package/template/sdk-kit/templates/theme/src/Header.tsx +47 -0
- package/template/sdk-kit/templates/theme/src/Views.tsx +36 -0
- package/template/sdk-kit/templates/theme/src/blocks.tsx +93 -0
- package/template/sdk-kit/templates/theme/src/index.tsx +30 -0
- package/template/sdk-kit/templates/theme/src/site.tsx +59 -0
- package/template/sdk-kit/templates/theme/src/styles.css +71 -0
- package/template/server/client/routes.ts +252 -0
- package/template/server/cms/analyticsStore.ts +180 -0
- package/template/server/cms/auth.ts +155 -0
- package/template/server/cms/blockCatalog.ts +342 -0
- package/template/server/cms/blocks.ts +521 -0
- package/template/server/cms/contentMigration.ts +1119 -0
- package/template/server/cms/defaults.ts +303 -0
- package/template/server/cms/permissions.ts +154 -0
- package/template/server/cms/restoreClassicHome.ts +183 -0
- package/template/server/cms/revisions.ts +99 -0
- package/template/server/cms/scheduler.ts +78 -0
- package/template/server/cms/seed.ts +72 -0
- package/template/server/cms/seoAnalyzer.ts +213 -0
- package/template/server/cms/store.ts +122 -0
- package/template/server/cms/themeContent.ts +1386 -0
- package/template/server/cms/themePacks/sidibou-branding.json +1 -0
- package/template/server/cms/themePacks/sidibou-home.json +1 -0
- package/template/server/cms/themePacks/sidibou-menus.json +1 -0
- package/template/server/cms/types.ts +432 -0
- package/template/server/community/routes.ts +410 -0
- package/template/server/db/client.ts +43 -0
- package/template/server/db/repository.ts +389 -0
- package/template/server/db/schema.ts +726 -0
- package/template/server/demo/cmsReset.ts +196 -0
- package/template/server/developers/sdk.ts +85 -0
- package/template/server/geoip.ts +15 -0
- package/template/server/http.ts +53 -0
- package/template/server/mail.ts +216 -0
- package/template/server/marketplace/catalog.ts +74 -0
- package/template/server/marketplace/packageValidate.ts +475 -0
- package/template/server/marketplace/payments.ts +193 -0
- package/template/server/marketplace/routes.ts +850 -0
- package/template/server/marketplace/signing.ts +116 -0
- package/template/server/marketplace/syncOfficial.ts +282 -0
- package/template/server/media/images.ts +63 -0
- package/template/server/middleware/adminAuth.ts +134 -0
- package/template/server/modules/legacy.ts +44 -0
- package/template/server/modules/manager.ts +1122 -0
- package/template/server/modules/marketplace.ts +178 -0
- package/template/server/modules/outbound.ts +178 -0
- package/template/server/modules/purchases.ts +149 -0
- package/template/server/modules/records.ts +168 -0
- package/template/server/modules/themeHtml.ts +32 -0
- package/template/server/modules/trust.ts +16 -0
- package/template/server/modules/types.ts +147 -0
- package/template/server/platform/auth.ts +223 -0
- package/template/server/platform/clientWelcome.ts +168 -0
- package/template/server/platform/clients.ts +139 -0
- package/template/server/platform/dailyImprove.ts +356 -0
- package/template/server/platform/flouci.ts +126 -0
- package/template/server/platform/host.ts +69 -0
- package/template/server/platform/hostingProviders.ts +143 -0
- package/template/server/platform/licenseOrders.ts +291 -0
- package/template/server/platform/money.ts +42 -0
- package/template/server/platform/passwords.ts +41 -0
- package/template/server/platform/region.ts +86 -0
- package/template/server/platform/routes.ts +225 -0
- package/template/server/platform/seed.ts +82 -0
- package/template/server/platform/settings.ts +72 -0
- package/template/server/routes/adminAccount.ts +403 -0
- package/template/server/routes/adminCms.ts +808 -0
- package/template/server/routes/adminSystem.ts +243 -0
- package/template/server/routes/adminUsers.ts +449 -0
- package/template/server/routes/googleOAuth.ts +327 -0
- package/template/server/routes/modules.ts +323 -0
- package/template/server/routes/publicCms.ts +313 -0
- package/template/server/routes/seoPublic.ts +158 -0
- package/template/server/security/accounts.ts +121 -0
- package/template/server/security/audit.ts +143 -0
- package/template/server/security/authTokens.ts +74 -0
- package/template/server/security/ip.ts +68 -0
- package/template/server/security/rateLimit.ts +85 -0
- package/template/server/security/retention.ts +46 -0
- package/template/server/security/secrets.ts +44 -0
- package/template/server/security/sessions.ts +140 -0
- package/template/server/security/totp.ts +101 -0
- package/template/server/superadmin/analytics.ts +109 -0
- package/template/server/superadmin/auth.ts +92 -0
- package/template/server/superadmin/backlinksCatalog.ts +210 -0
- package/template/server/superadmin/frSeoContent.ts +165 -0
- package/template/server/superadmin/landingSeo.ts +675 -0
- package/template/server/superadmin/ops.ts +238 -0
- package/template/server/superadmin/routes.ts +1095 -0
- package/template/server/superadmin/tnSeoContent.ts +130 -0
- package/template/server.ts +522 -0
- package/template/src/App.tsx +263 -0
- package/template/src/admin/AdminApp.tsx +162 -0
- package/template/src/admin/account/AuthLayout.tsx +57 -0
- package/template/src/admin/account/SecurityForms.tsx +276 -0
- package/template/src/admin/admin.css +127 -0
- package/template/src/admin/context/AuthContext.tsx +147 -0
- package/template/src/admin/context/SiteContext.tsx +50 -0
- package/template/src/admin/editor/BlockLibrary.tsx +173 -0
- package/template/src/admin/editor/BlocksPanel.tsx +396 -0
- package/template/src/admin/editor/EditorBits.tsx +106 -0
- package/template/src/admin/editor/PreviewFrame.tsx +145 -0
- package/template/src/admin/editor/RichTextEditor.tsx +276 -0
- package/template/src/admin/editor/SeoPanel.tsx +349 -0
- package/template/src/admin/editor/StyleEditor.tsx +114 -0
- package/template/src/admin/editor/blockSchemas.ts +699 -0
- package/template/src/admin/editor/fields.tsx +486 -0
- package/template/src/admin/layout/AdminShell.tsx +418 -0
- package/template/src/admin/layout/nav.ts +95 -0
- package/template/src/admin/lib/api.ts +408 -0
- package/template/src/admin/lib/audit.ts +141 -0
- package/template/src/admin/lib/format.ts +84 -0
- package/template/src/admin/lib/hooks.ts +125 -0
- package/template/src/admin/main.tsx +16 -0
- package/template/src/admin/modules/InstallDialog.tsx +160 -0
- package/template/src/admin/modules/SettingsForm.tsx +145 -0
- package/template/src/admin/modules/kit.tsx +127 -0
- package/template/src/admin/modules/labels.tsx +226 -0
- package/template/src/admin/pages/AccountSetupPage.tsx +62 -0
- package/template/src/admin/pages/AnalyticsPage.tsx +206 -0
- package/template/src/admin/pages/BrandingPage.tsx +154 -0
- package/template/src/admin/pages/DashboardPage.tsx +239 -0
- package/template/src/admin/pages/EngagementPage.tsx +168 -0
- package/template/src/admin/pages/FormsPage.tsx +435 -0
- package/template/src/admin/pages/GeneralPage.tsx +120 -0
- package/template/src/admin/pages/LoginPage.tsx +249 -0
- package/template/src/admin/pages/MaintenancePage.tsx +125 -0
- package/template/src/admin/pages/MarketplacePage.tsx +426 -0
- package/template/src/admin/pages/MediaPage.tsx +251 -0
- package/template/src/admin/pages/MenusPage.tsx +446 -0
- package/template/src/admin/pages/ModuleAdminPage.tsx +113 -0
- package/template/src/admin/pages/ModuleDetailPage.tsx +293 -0
- package/template/src/admin/pages/ModulesPage.tsx +135 -0
- package/template/src/admin/pages/NewsletterPage.tsx +193 -0
- package/template/src/admin/pages/PageEditorPage.tsx +322 -0
- package/template/src/admin/pages/PagesListPage.tsx +277 -0
- package/template/src/admin/pages/PostEditorPage.tsx +292 -0
- package/template/src/admin/pages/PostsListPage.tsx +187 -0
- package/template/src/admin/pages/ProfilePage.tsx +489 -0
- package/template/src/admin/pages/ResetPasswordPage.tsx +110 -0
- package/template/src/admin/pages/SeoPage.tsx +670 -0
- package/template/src/admin/pages/SettingsPage.tsx +189 -0
- package/template/src/admin/pages/ThemeCustomizePage.tsx +170 -0
- package/template/src/admin/pages/ThemesPage.tsx +292 -0
- package/template/src/admin/pages/UsersPage.tsx +57 -0
- package/template/src/admin/pages/WidgetsPage.tsx +200 -0
- package/template/src/admin/seo/GoogleSetup.tsx +529 -0
- package/template/src/admin/settings/EmailTab.tsx +293 -0
- package/template/src/admin/settings/OtherTabs.tsx +270 -0
- package/template/src/admin/settings/SecurityTab.tsx +172 -0
- package/template/src/admin/settings/controls.tsx +133 -0
- package/template/src/admin/ui/Pagination.tsx +170 -0
- package/template/src/admin/ui/SaveBar.tsx +77 -0
- package/template/src/admin/ui/TagInput.tsx +54 -0
- package/template/src/admin/ui/charts.tsx +108 -0
- package/template/src/admin/ui/media.tsx +265 -0
- package/template/src/admin/ui/overlays.tsx +239 -0
- package/template/src/admin/ui/primitives.tsx +544 -0
- package/template/src/admin/users/AuditTab.tsx +104 -0
- package/template/src/admin/users/RolesTab.tsx +368 -0
- package/template/src/admin/users/UsersTab.tsx +723 -0
- package/template/src/admin/users/shared.tsx +278 -0
- package/template/src/cms/icons.ts +7 -0
- package/template/src/components/CookieBanner.tsx +166 -0
- package/template/src/components/MaintenanceGate.tsx +66 -0
- package/template/src/components/SiteAnalytics.tsx +64 -0
- package/template/src/components/SnapifyLogo.tsx +44 -0
- package/template/src/components/cms/BlockRenderer.tsx +154 -0
- package/template/src/components/cms/WidgetArea.tsx +193 -0
- package/template/src/context/SiteConfigContext.tsx +130 -0
- package/template/src/demoBase.ts +23 -0
- package/template/src/index.css +90 -0
- package/template/src/main.tsx +16 -0
- package/template/src/modules/ModuleHost.tsx +197 -0
- package/template/src/preview.ts +63 -0
- package/template/src/runtime.ts +38 -0
- package/template/src/themes/ThemeHost.tsx +168 -0
- package/template/src/themes/fallback.tsx +71 -0
- package/template/src/types.ts +94 -0
- package/template/src/views/BlogPostView.tsx +68 -0
- package/template/src/views/CmsPageView.tsx +34 -0
- package/template/start-pm2.sh +3 -0
- package/template/superadmin.html +20 -0
- package/template/themes/ai-canvas/screenshot.jpg +0 -0
- package/template/themes/ai-canvas/snapify.theme.json +96 -0
- package/template/themes/ai-canvas/src/CeeSimulator.tsx +247 -0
- package/template/themes/ai-canvas/src/Footer.tsx +190 -0
- package/template/themes/ai-canvas/src/Header.tsx +324 -0
- package/template/themes/ai-canvas/src/Layout.tsx +56 -0
- package/template/themes/ai-canvas/src/NotFound.tsx +58 -0
- package/template/themes/ai-canvas/src/Post.tsx +26 -0
- package/template/themes/ai-canvas/src/SnapifyLogo.tsx +44 -0
- package/template/themes/ai-canvas/src/blocks/CollectionBlocks.tsx +711 -0
- package/template/themes/ai-canvas/src/blocks/ContentBlocks.tsx +318 -0
- package/template/themes/ai-canvas/src/blocks/ConversionBlocks.tsx +559 -0
- package/template/themes/ai-canvas/src/blocks/HeaderBlocks.tsx +341 -0
- package/template/themes/ai-canvas/src/index.tsx +54 -0
- package/template/themes/ai-canvas/src/kit.tsx +398 -0
- package/template/themes/ai-canvas/src/site.ts +29 -0
- package/template/themes/ai-canvas/src/styles.css +317 -0
- package/template/themes/dogga/screenshot.jpg +0 -0
- package/template/themes/dogga/snapify.theme.json +55 -0
- package/template/themes/dogga/src/Brand.tsx +41 -0
- package/template/themes/dogga/src/Footer.tsx +202 -0
- package/template/themes/dogga/src/Header.tsx +239 -0
- package/template/themes/dogga/src/Layout.tsx +43 -0
- package/template/themes/dogga/src/Views.tsx +71 -0
- package/template/themes/dogga/src/blocks/CollectionBlocks.tsx +674 -0
- package/template/themes/dogga/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/dogga/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/dogga/src/blocks/HeaderBlocks.tsx +289 -0
- package/template/themes/dogga/src/index.tsx +53 -0
- package/template/themes/dogga/src/kit.tsx +362 -0
- package/template/themes/dogga/src/site.ts +56 -0
- package/template/themes/dogga/src/styles.css +396 -0
- package/template/themes/mahdia/screenshot.jpg +0 -0
- package/template/themes/mahdia/snapify.theme.json +65 -0
- package/template/themes/mahdia/src/Brand.tsx +39 -0
- package/template/themes/mahdia/src/Footer.tsx +210 -0
- package/template/themes/mahdia/src/Header.tsx +291 -0
- package/template/themes/mahdia/src/Layout.tsx +44 -0
- package/template/themes/mahdia/src/Views.tsx +79 -0
- package/template/themes/mahdia/src/blocks/CollectionBlocks.tsx +674 -0
- package/template/themes/mahdia/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/mahdia/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/mahdia/src/blocks/HeaderBlocks.tsx +289 -0
- package/template/themes/mahdia/src/index.tsx +53 -0
- package/template/themes/mahdia/src/kit.tsx +362 -0
- package/template/themes/mahdia/src/site.ts +75 -0
- package/template/themes/mahdia/src/styles.css +446 -0
- package/template/themes/preveris/screenshot.jpg +0 -0
- package/template/themes/preveris/snapify.theme.json +126 -0
- package/template/themes/preveris/src/Brand.tsx +44 -0
- package/template/themes/preveris/src/Footer.tsx +202 -0
- package/template/themes/preveris/src/Header.tsx +239 -0
- package/template/themes/preveris/src/Layout.tsx +42 -0
- package/template/themes/preveris/src/Views.tsx +71 -0
- package/template/themes/preveris/src/blocks/CollectionBlocks.tsx +678 -0
- package/template/themes/preveris/src/blocks/ContentBlocks.tsx +307 -0
- package/template/themes/preveris/src/blocks/ConversionBlocks.tsx +571 -0
- package/template/themes/preveris/src/blocks/HeaderBlocks.tsx +299 -0
- package/template/themes/preveris/src/index.tsx +53 -0
- package/template/themes/preveris/src/kit.tsx +365 -0
- package/template/themes/preveris/src/site.ts +79 -0
- package/template/themes/preveris/src/styles.css +446 -0
- package/template/themes/sidibou/screenshot.jpg +0 -0
- package/template/themes/sidibou/snapify.theme.json +21 -0
- package/template/themes/sidibou/src/CeeSimulator.tsx +247 -0
- package/template/themes/sidibou/src/Footer.tsx +203 -0
- package/template/themes/sidibou/src/Header.tsx +324 -0
- package/template/themes/sidibou/src/Layout.tsx +53 -0
- package/template/themes/sidibou/src/NotFound.tsx +58 -0
- package/template/themes/sidibou/src/Post.tsx +26 -0
- package/template/themes/sidibou/src/SnapifyLogo.tsx +44 -0
- package/template/themes/sidibou/src/blocks/CollectionBlocks.tsx +711 -0
- package/template/themes/sidibou/src/blocks/ContentBlocks.tsx +318 -0
- package/template/themes/sidibou/src/blocks/ConversionBlocks.tsx +559 -0
- package/template/themes/sidibou/src/blocks/HeaderBlocks.tsx +341 -0
- package/template/themes/sidibou/src/index.tsx +54 -0
- package/template/themes/sidibou/src/kit.tsx +398 -0
- package/template/themes/sidibou/src/site.ts +28 -0
- package/template/themes/sidibou/src/styles.css +317 -0
- package/template/tsconfig.json +33 -0
- package/template/vite.config.ts +42 -0
|
@@ -0,0 +1,1587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify Developers — contenu de la documentation
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useState, type ComponentType } from 'react';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
7
|
+
import { C, Code, H2, H3, MARKETPLACE_URL, NextLink, Note, SDK_DOWNLOAD_URL, Steps, Table } from './ui';
|
|
8
|
+
|
|
9
|
+
export const API_VERSION = '1.3.0';
|
|
10
|
+
|
|
11
|
+
export interface DocPage {
|
|
12
|
+
slug: string;
|
|
13
|
+
section: string;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
keywords?: string;
|
|
17
|
+
Body: ComponentType;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type MpSettings = { commissionBps: number; developerFeeLabel: string };
|
|
21
|
+
|
|
22
|
+
function useMarketplaceSettings() {
|
|
23
|
+
const [s, setS] = useState<MpSettings | null>(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
fetch('/api/marketplace/settings/public')
|
|
26
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
27
|
+
.then((d) => d && setS(d))
|
|
28
|
+
.catch(() => undefined);
|
|
29
|
+
}, []);
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const product = (id: string) => `${MARKETPLACE_URL}/product/${id}`;
|
|
34
|
+
|
|
35
|
+
/* ------------------------------------------------------------------ Démarrer */
|
|
36
|
+
|
|
37
|
+
function Introduction() {
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<p>
|
|
41
|
+
Snapify est un CMS tunisien conçu pour être étendu sans toucher à son cœur. Deux types d’extensions existent, distribuées sous forme de
|
|
42
|
+
paquets signés et vendues ou offertes sur la <a href={MARKETPLACE_URL}>marketplace</a> :
|
|
43
|
+
</p>
|
|
44
|
+
<Table
|
|
45
|
+
head={['', 'Module', 'Thème']}
|
|
46
|
+
rows={[
|
|
47
|
+
['Rôle', 'Ajoute une fonctionnalité : widget, page, API, administration', 'Fournit toute la présentation du site'],
|
|
48
|
+
['Paquet', <C>.snapmod</C>, <C>.snaptheme</C>],
|
|
49
|
+
['Manifeste', <C>snapify.module.json</C>, <C>snapify.theme.json</C>],
|
|
50
|
+
['Code serveur', 'Oui (facultatif), avec permissions', 'Non'],
|
|
51
|
+
['Actifs en même temps', 'Autant que nécessaire', 'Un seul'],
|
|
52
|
+
]}
|
|
53
|
+
/>
|
|
54
|
+
<H2>Comment ça marche</H2>
|
|
55
|
+
<p>
|
|
56
|
+
Le site hôte charge les données, gère la navigation, l’aperçu de l’éditeur, les cookies et la sécurité. Votre extension ne fait que ce
|
|
57
|
+
qu’elle déclare dans son manifeste :
|
|
58
|
+
</p>
|
|
59
|
+
<ul>
|
|
60
|
+
<li>
|
|
61
|
+
<strong>Côté navigateur</strong>, un module affiche des composants React dans des emplacements (<C>floating</C>,{' '}
|
|
62
|
+
<C>header-actions</C>), des pages complètes (<C>/agenda/*</C>) ou des widgets que l’éditeur insère dans une page.
|
|
63
|
+
</li>
|
|
64
|
+
<li>
|
|
65
|
+
<strong>Côté serveur</strong>, il reçoit un contexte (<C>ctx</C>) qui donne accès aux routes HTTP, à une base de données propre, aux
|
|
66
|
+
e-mails… uniquement pour les permissions demandées.
|
|
67
|
+
</li>
|
|
68
|
+
<li>
|
|
69
|
+
<strong>Dans l’administration</strong>, ses réglages deviennent un formulaire généré automatiquement, et il peut ajouter ses propres
|
|
70
|
+
pages au menu.
|
|
71
|
+
</li>
|
|
72
|
+
</ul>
|
|
73
|
+
<H2>Le SDK</H2>
|
|
74
|
+
<p>Les sources importent uniquement React, le SDK Snapify et des dépendances qui seront intégrées au bundle (ex. <C>lucide-react</C>).</p>
|
|
75
|
+
<Table
|
|
76
|
+
head={['Import', 'Contenu']}
|
|
77
|
+
rows={[
|
|
78
|
+
[<C>@snapify/sdk</C>, 'Manifeste, partie publique (definePublicModule) et serveur (defineServerModule), types'],
|
|
79
|
+
[<C>@snapify/sdk/admin</C>, 'Pages d’administration (defineAdminModule) et composants du back-office'],
|
|
80
|
+
[<C>@snapify/sdk/theme</C>, 'Contrat des thèmes : defineTheme, useSnapify, ModuleSlot'],
|
|
81
|
+
[<C>@snapify/sdk/content</C>, 'Types des blocs, pages, articles et de la configuration du site'],
|
|
82
|
+
[<C>@snapify/sdk/commerce</C>, 'Contrat e-commerce partagé (panier, client, adresses, statuts de commande)'],
|
|
83
|
+
[<C>@snapify/sdk/image</C>, 'Préparation d’images dans le navigateur avant envoi (prepareAvatar)'],
|
|
84
|
+
[<C>@snapify/sdk/icons</C>, 'Icônes disponibles dans l’éditeur de blocs'],
|
|
85
|
+
]}
|
|
86
|
+
/>
|
|
87
|
+
<Note title={`Version de l’API : ${API_VERSION}`}>
|
|
88
|
+
Déclarez la version minimale dont vous avez besoin dans <C>engines.snapify</C> (ex. <C>^1.2.0</C>). Un site plus ancien refusera
|
|
89
|
+
l’installation au lieu de planter.
|
|
90
|
+
</Note>
|
|
91
|
+
<Note tone="tip" title="Kit développeur">
|
|
92
|
+
Le SDK et l’outil <C>snapify</C> (création de projet, compilation, analyse marketplace) sont gratuits :{' '}
|
|
93
|
+
lancez <C>npm create snapify@latest</C> ou consultez la page <Link to="/docs/kit-developpeur">Kit développeur</Link>.
|
|
94
|
+
</Note>
|
|
95
|
+
<div className="flex flex-wrap gap-3 pt-2">
|
|
96
|
+
<NextLink to="/docs/kit-developpeur" label="Installer le kit" />
|
|
97
|
+
<NextLink to="/docs/demarrage-rapide" label="Créer votre premier module" />
|
|
98
|
+
<NextLink to="/docs/themes/creer-un-theme" label="Créer un thème" />
|
|
99
|
+
</div>
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type SdkFile = { name: string; kind: 'sdk' | 'starter'; size: number; sha256: string };
|
|
105
|
+
type SdkRelease = { version: string; api: string; date: string; checksums: string; npm?: { sdk: string; create: string }; files: SdkFile[] };
|
|
106
|
+
|
|
107
|
+
function useSdkReleases() {
|
|
108
|
+
const [state, setState] = useState<{ latest?: SdkRelease; releases: SdkRelease[]; failed: boolean }>({ releases: [], failed: false });
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
fetch('/api/developers/sdk')
|
|
111
|
+
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
|
|
112
|
+
.then((d: { latest: string; releases: SdkRelease[] }) => setState({ latest: d.releases.find((r) => r.version === d.latest), releases: d.releases, failed: false }))
|
|
113
|
+
.catch(() => setState({ releases: [], failed: true }));
|
|
114
|
+
}, []);
|
|
115
|
+
return state;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sdkUrl = (file: string) => `${SDK_DOWNLOAD_URL}/${file}`;
|
|
119
|
+
const NPM_URL = 'https://www.npmjs.com/package';
|
|
120
|
+
const kb = (n: number) => `${Math.round(n / 1024)} Ko`;
|
|
121
|
+
const longDate = (iso: string) => new Date(iso).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
122
|
+
|
|
123
|
+
function DownloadCard({ file, title, text }: { file?: SdkFile; title: string; text: string }) {
|
|
124
|
+
return (
|
|
125
|
+
<div className="flex flex-col rounded-xl border border-slate-200 p-5">
|
|
126
|
+
<div className="font-semibold text-navy">{title}</div>
|
|
127
|
+
<p className="mt-1 flex-1 text-sm text-slate-600">{text}</p>
|
|
128
|
+
{file ? (
|
|
129
|
+
<>
|
|
130
|
+
<a href={`/sdk/${file.name}`} download className="mt-4 inline-flex items-center justify-center rounded-lg bg-navy px-4 py-2 text-sm font-semibold text-white no-underline hover:bg-navy-2">
|
|
131
|
+
Télécharger {file.name.endsWith('.zip') ? '.zip' : '.tgz'} · {kb(file.size)}
|
|
132
|
+
</a>
|
|
133
|
+
<div className="mt-3 break-all font-mono text-[11px] leading-snug text-slate-500" title="Empreinte SHA-256">
|
|
134
|
+
SHA-256 {file.sha256}
|
|
135
|
+
</div>
|
|
136
|
+
</>
|
|
137
|
+
) : (
|
|
138
|
+
<div className="mt-4 h-9 animate-pulse rounded-lg bg-slate-100" />
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function KitDownload() {
|
|
145
|
+
const { latest, releases, failed } = useSdkReleases();
|
|
146
|
+
const version = latest?.version ?? API_VERSION;
|
|
147
|
+
const sdk = latest?.files.find((f) => f.kind === 'sdk');
|
|
148
|
+
const starter = latest?.files.find((f) => f.kind === 'starter');
|
|
149
|
+
const tarball = sdkUrl(sdk?.name ?? `snapify-sdk-${version}.tgz`);
|
|
150
|
+
const npmName = latest?.npm?.sdk ?? 'snapify-sdk';
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
<p>
|
|
155
|
+
Le kit développeur réunit le SDK et ses déclarations TypeScript, l’outil en ligne de commande <C>snapify</C> et les modèles de projet.
|
|
156
|
+
Il est gratuit et publié par NOVALABS sur le registre npm sous le nom{' '}
|
|
157
|
+
<a href={`${NPM_URL}/${npmName}`} target="_blank" rel="noreferrer">
|
|
158
|
+
<C>{npmName}</C>
|
|
159
|
+
</a>
|
|
160
|
+
.
|
|
161
|
+
</p>
|
|
162
|
+
{failed ? (
|
|
163
|
+
<Note tone="warn" title="Liste des versions indisponible">
|
|
164
|
+
Réessayez dans un instant. Les commandes npm ci-dessous restent valables.
|
|
165
|
+
</Note>
|
|
166
|
+
) : (
|
|
167
|
+
<p className="text-sm text-slate-500">
|
|
168
|
+
Version <strong className="text-navy">{latest?.version ?? '…'}</strong>
|
|
169
|
+
{latest ? <> · API {latest.api} · publiée le {longDate(latest.date)}</> : null}
|
|
170
|
+
</p>
|
|
171
|
+
)}
|
|
172
|
+
<Code lang="bash" title="nouveau projet">{`npm create snapify@latest`}</Code>
|
|
173
|
+
<p>
|
|
174
|
+
L’outil demande s’il s’agit d’un module ou d’un thème, le dossier et votre nom d’éditeur, puis prépare le projet : manifeste, sources
|
|
175
|
+
React et TypeScript, scripts npm.
|
|
176
|
+
</p>
|
|
177
|
+
|
|
178
|
+
<H2>Prérequis</H2>
|
|
179
|
+
<p>
|
|
180
|
+
Node.js <strong>20.19</strong> ou <strong>22.12</strong> et plus récent, npm 10 ou plus. React, Tailwind et Vite sont installés avec le
|
|
181
|
+
kit : rien d’autre à configurer.
|
|
182
|
+
</p>
|
|
183
|
+
|
|
184
|
+
<H2>Créer un projet</H2>
|
|
185
|
+
<Code lang="bash">{`
|
|
186
|
+
npm create snapify@latest
|
|
187
|
+
cd mon-module
|
|
188
|
+
npm install
|
|
189
|
+
npm run build
|
|
190
|
+
`}</Code>
|
|
191
|
+
<p>
|
|
192
|
+
<C>npm run build</C> produit <C>dist/monagence.mon-module-1.0.0.snapmod</C> puis lance <strong>la même analyse que la marketplace</strong>{' '}
|
|
193
|
+
(manifeste, permissions, code, sécurité, présentation) : un paquet qui passe sur votre poste passe à l’envoi.
|
|
194
|
+
</p>
|
|
195
|
+
<p>Sans questions, pour un script ou une intégration continue :</p>
|
|
196
|
+
<Code lang="bash">{`
|
|
197
|
+
# Module (remplacez « monagence » par votre nom d’éditeur)
|
|
198
|
+
npm create snapify@latest mon-module -- --id monagence.mon-module
|
|
199
|
+
|
|
200
|
+
# Thème
|
|
201
|
+
npm create snapify@latest mon-theme -- --theme --id monagence.mon-theme
|
|
202
|
+
`}</Code>
|
|
203
|
+
|
|
204
|
+
<H2>Ajouter le SDK à un projet existant</H2>
|
|
205
|
+
<p>
|
|
206
|
+
Le code des extensions importe le SDK sous le nom <C>@snapify/sdk</C>. Installez le paquet <C>{npmName}</C> sous ce nom :
|
|
207
|
+
</p>
|
|
208
|
+
<Code lang="bash">{`npm install --save-dev @snapify/sdk@npm:${npmName}@^${version}`}</Code>
|
|
209
|
+
<Note tone="warn" title="N’installez jamais « @snapify/sdk » seul">
|
|
210
|
+
Sur le registre npm, le nom <C>@snapify/sdk</C> n’appartient pas à NOVALABS. Le paquet officiel est <C>{npmName}</C> : utilisez toujours
|
|
211
|
+
l’alias ci-dessus, jamais <C>npm install @snapify/sdk</C> sans <C>npm:{npmName}</C>.
|
|
212
|
+
</Note>
|
|
213
|
+
|
|
214
|
+
<H2>Contenu du kit</H2>
|
|
215
|
+
<Table
|
|
216
|
+
head={['Élément', 'Rôle']}
|
|
217
|
+
rows={[
|
|
218
|
+
[<C>@snapify/sdk</C>, <>Imports des modules et des thèmes (<C>/admin</C>, <C>/theme</C>, <C>/content</C>…), avec leurs types</>],
|
|
219
|
+
[<C>snapify create</C>, 'Nouveau projet de module ou de thème : manifeste, sources, TypeScript, scripts npm'],
|
|
220
|
+
[<C>snapify build</C>, 'Compilation du paquet .snapmod / .snaptheme, puis analyse marketplace'],
|
|
221
|
+
[<C>snapify check</C>, 'Analyse d’un paquet existant, identique à celle de la marketplace'],
|
|
222
|
+
[<C>snapify verify</C>, 'Signature NOVALABS et licence d’un paquet reçu'],
|
|
223
|
+
]}
|
|
224
|
+
/>
|
|
225
|
+
<p>
|
|
226
|
+
Les paquets produits ne sont pas signés : ils s’installent sur un site Snapify en mode développeur. NOVALABS signe la version validée
|
|
227
|
+
lors de la <Link to="/docs/publier">revue marketplace</Link>.
|
|
228
|
+
</p>
|
|
229
|
+
|
|
230
|
+
<H2>Téléchargement direct</H2>
|
|
231
|
+
<p>
|
|
232
|
+
Pour un poste sans accès au registre npm, ou pour archiver une version, le kit et les projets de départ sont aussi disponibles ici.
|
|
233
|
+
L’archive du kit est identique à celle publiée sur npm.
|
|
234
|
+
</p>
|
|
235
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
236
|
+
<DownloadCard file={sdk} title="Kit développeur" text="Paquet npm : SDK, types, outil snapify et modèles. À installer avec npm install plutôt qu’à décompresser." />
|
|
237
|
+
<DownloadCard file={starter} title="Projets de départ" text="Un module et un thème prêts à compiler, pour explorer le code avant de créer le vôtre." />
|
|
238
|
+
</div>
|
|
239
|
+
<Code lang="bash">{`
|
|
240
|
+
# Installation depuis l’archive
|
|
241
|
+
npm install --save-dev @snapify/sdk@${tarball}
|
|
242
|
+
|
|
243
|
+
# Vérification des empreintes SHA-256
|
|
244
|
+
curl -O ${tarball}
|
|
245
|
+
curl -O ${sdkUrl(latest?.checksums ?? `SHA256SUMS-${version}.txt`)}
|
|
246
|
+
sha256sum -c --ignore-missing ${latest?.checksums ?? `SHA256SUMS-${version}.txt`}
|
|
247
|
+
|
|
248
|
+
# Windows (PowerShell)
|
|
249
|
+
Get-FileHash ${sdk?.name ?? `snapify-sdk-${version}.tgz`} -Algorithm SHA256
|
|
250
|
+
`}</Code>
|
|
251
|
+
|
|
252
|
+
<H2>Licence</H2>
|
|
253
|
+
<p>
|
|
254
|
+
Le kit est gratuit pour développer, tester et distribuer des extensions Snapify. Les extensions que vous créez vous appartiennent. Les
|
|
255
|
+
conditions complètes figurent dans le fichier <C>LICENSE.md</C> du paquet.
|
|
256
|
+
</p>
|
|
257
|
+
|
|
258
|
+
{releases.length ? (
|
|
259
|
+
<>
|
|
260
|
+
<H2>Versions</H2>
|
|
261
|
+
<Table
|
|
262
|
+
head={['Version', 'API', 'Date', 'npm', 'Fichiers']}
|
|
263
|
+
rows={releases.map((r) => [
|
|
264
|
+
<strong>{r.version}</strong>,
|
|
265
|
+
r.api,
|
|
266
|
+
longDate(r.date),
|
|
267
|
+
r.npm ? (
|
|
268
|
+
<a href={`${NPM_URL}/${r.npm.sdk}/v/${r.version}`} target="_blank" rel="noreferrer">
|
|
269
|
+
{r.npm.sdk}@{r.version}
|
|
270
|
+
</a>
|
|
271
|
+
) : (
|
|
272
|
+
'—'
|
|
273
|
+
),
|
|
274
|
+
<span className="flex flex-wrap gap-x-3">
|
|
275
|
+
{r.files.map((f) => (
|
|
276
|
+
<a key={f.name} href={`/sdk/${f.name}`} download>
|
|
277
|
+
{f.name}
|
|
278
|
+
</a>
|
|
279
|
+
))}
|
|
280
|
+
<a href={`/sdk/${r.checksums}`}>{r.checksums}</a>
|
|
281
|
+
</span>,
|
|
282
|
+
])}
|
|
283
|
+
/>
|
|
284
|
+
</>
|
|
285
|
+
) : null}
|
|
286
|
+
|
|
287
|
+
<div className="flex flex-wrap gap-3 pt-2">
|
|
288
|
+
<NextLink to="/docs/demarrage-rapide" label="Créer votre premier module" />
|
|
289
|
+
<NextLink to="/docs/reference/cli" label="Toutes les commandes" />
|
|
290
|
+
</div>
|
|
291
|
+
</>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function Quickstart() {
|
|
296
|
+
return (
|
|
297
|
+
<>
|
|
298
|
+
<p>
|
|
299
|
+
En quelques minutes, nous allons créer <strong>Bonjour</strong> : une bannière flottante dont le texte se règle dans l’administration et
|
|
300
|
+
qui compte les clics côté serveur.
|
|
301
|
+
</p>
|
|
302
|
+
<H2>1. Créer le projet</H2>
|
|
303
|
+
<p>
|
|
304
|
+
Avec le <Link to="/docs/kit-developpeur">kit développeur</Link> (Node.js 20.19+), une commande prépare le projet, ses dépendances et
|
|
305
|
+
TypeScript :
|
|
306
|
+
</p>
|
|
307
|
+
<Code lang="bash">{`
|
|
308
|
+
npm create snapify@latest bonjour -- --id monagence.bonjour --name "Bonjour"
|
|
309
|
+
cd bonjour
|
|
310
|
+
npm install
|
|
311
|
+
`}</Code>
|
|
312
|
+
<Code lang="text" title="structure">{`
|
|
313
|
+
bonjour/
|
|
314
|
+
├── package.json # scripts build et typecheck, SDK en dépendance
|
|
315
|
+
├── tsconfig.json
|
|
316
|
+
├── snapify.module.json # manifeste (obligatoire)
|
|
317
|
+
├── README.md # inclus dans le paquet
|
|
318
|
+
└── src/
|
|
319
|
+
├── public.tsx # affiché sur le site
|
|
320
|
+
├── server.ts # code serveur (facultatif)
|
|
321
|
+
└── admin.tsx # pages d’administration (facultatif)
|
|
322
|
+
`}</Code>
|
|
323
|
+
<p>Le modèle contient déjà une bannière, un widget et une page de statistiques. Les étapes suivantes en expliquent l’essentiel.</p>
|
|
324
|
+
<H2>2. Écrire le manifeste</H2>
|
|
325
|
+
<Code lang="json" title="snapify.module.json">{`
|
|
326
|
+
{
|
|
327
|
+
"id": "monagence.bonjour",
|
|
328
|
+
"name": "Bonjour",
|
|
329
|
+
"version": "1.0.0",
|
|
330
|
+
"description": "Bannière d’accueil flottante avec compteur de clics.",
|
|
331
|
+
"author": { "name": "Mon Agence", "email": "dev@monagence.tn", "url": "https://monagence.tn" },
|
|
332
|
+
"pricing": "free",
|
|
333
|
+
"category": "Marketing",
|
|
334
|
+
"icon": "sparkles",
|
|
335
|
+
"engines": { "snapify": "^1.3.0" },
|
|
336
|
+
"permissions": ["http:routes", "storage"],
|
|
337
|
+
"slots": ["floating"],
|
|
338
|
+
"entries": { "public": "src/public.tsx", "server": "src/server.ts" },
|
|
339
|
+
"settings": [
|
|
340
|
+
{ "key": "message", "type": "text", "group": "Bannière", "label": "Message", "default": "Bienvenue !", "public": true, "maxLength": 80 },
|
|
341
|
+
{ "key": "showOnPages", "type": "paths", "group": "Bannière", "label": "Pages", "default": [], "public": true }
|
|
342
|
+
]
|
|
343
|
+
}
|
|
344
|
+
`}</Code>
|
|
345
|
+
<H2>3. La partie publique</H2>
|
|
346
|
+
<Code lang="tsx" title="src/public.tsx">{`
|
|
347
|
+
import { definePublicModule, matchesPaths, type PublicModuleProps } from '@snapify/sdk';
|
|
348
|
+
|
|
349
|
+
interface Settings { message: string; showOnPages: string[] }
|
|
350
|
+
|
|
351
|
+
function Banner({ settings, path, isPreview, api }: PublicModuleProps<Settings>) {
|
|
352
|
+
if (isPreview || !matchesPaths(settings.showOnPages, path)) return null;
|
|
353
|
+
return (
|
|
354
|
+
<button
|
|
355
|
+
className="fixed bottom-5 left-5 rounded-full bg-px-primary px-5 py-3 font-semibold text-white shadow-lg"
|
|
356
|
+
onClick={() => api.post('/click')}
|
|
357
|
+
>
|
|
358
|
+
{settings.message}
|
|
359
|
+
</button>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export default definePublicModule<Settings>({ slots: { floating: Banner } });
|
|
364
|
+
`}</Code>
|
|
365
|
+
<H2>4. La partie serveur</H2>
|
|
366
|
+
<Code lang="ts" title="src/server.ts">{`
|
|
367
|
+
import { defineServerModule, type ServerContext } from '@snapify/sdk';
|
|
368
|
+
|
|
369
|
+
export default defineServerModule({
|
|
370
|
+
activate(ctx: ServerContext) {
|
|
371
|
+
ctx.http.post('/click', async () => {
|
|
372
|
+
const clicks = ((await ctx.storage.get<number>('clicks')) ?? 0) + 1;
|
|
373
|
+
await ctx.storage.set('clicks', clicks);
|
|
374
|
+
return { clicks };
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
ctx.admin.status(async () => ({
|
|
378
|
+
stats: [{ label: 'Clics', value: (await ctx.storage.get<number>('clicks')) ?? 0 }],
|
|
379
|
+
}));
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
`}</Code>
|
|
383
|
+
<H2>5. Construire et tester</H2>
|
|
384
|
+
<Code lang="bash">{`
|
|
385
|
+
# Compile dist/monagence.bonjour-1.0.0.snapmod puis l’analyse comme la marketplace
|
|
386
|
+
npm run build
|
|
387
|
+
|
|
388
|
+
# Vérifie les types TypeScript
|
|
389
|
+
npm run typecheck
|
|
390
|
+
`}</Code>
|
|
391
|
+
<Code lang="text" title="sortie (extrait)">{`
|
|
392
|
+
✔ dist/monagence.bonjour-1.0.0.snapmod (0.9 s, non signé)
|
|
393
|
+
|
|
394
|
+
• Analyse marketplace
|
|
395
|
+
Permissions
|
|
396
|
+
✔ ctx.http (routes publiques) — permission http:routes déclarée
|
|
397
|
+
✔ Permissions minimales — toutes les permissions déclarées sont utilisées
|
|
398
|
+
…
|
|
399
|
+
✔ Analyse marketplace réussie : 23 contrôles, 0 erreur(s), 2 avertissement(s)
|
|
400
|
+
`}</Code>
|
|
401
|
+
<p>
|
|
402
|
+
Pour voir le module en fonctionnement, importez <C>dist/monagence.bonjour-1.0.0.snapmod</C> sur un site Snapify de test en mode
|
|
403
|
+
développeur (<strong>Administration › Modules › Importer un module</strong>), activez-le, puis réglez le message dans sa page. La page{' '}
|
|
404
|
+
<Link to="/docs/tester">Tester une extension</Link> détaille la marche à suivre, y compris sans installation Snapify.
|
|
405
|
+
</p>
|
|
406
|
+
<H2>Et ensuite ?</H2>
|
|
407
|
+
<div className="flex flex-wrap gap-3">
|
|
408
|
+
<NextLink to="/docs/tester" label="Tester une extension" />
|
|
409
|
+
<NextLink to="/docs/modules/site-public" label="Pages et widgets" />
|
|
410
|
+
<NextLink to="/docs/modules/base-de-donnees" label="Base de données" />
|
|
411
|
+
<NextLink to="/docs/publier" label="Publier sur la marketplace" />
|
|
412
|
+
</div>
|
|
413
|
+
</>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* ------------------------------------------------------------------ Modules */
|
|
418
|
+
|
|
419
|
+
function Manifest() {
|
|
420
|
+
return (
|
|
421
|
+
<>
|
|
422
|
+
<p>
|
|
423
|
+
Le manifeste <C>snapify.module.json</C> décrit le module : identité, permissions, emplacements, réglages. L’administration l’affiche
|
|
424
|
+
avant installation et le site le contrôle à chaque démarrage.
|
|
425
|
+
</p>
|
|
426
|
+
<H2>Champs</H2>
|
|
427
|
+
<Table
|
|
428
|
+
head={['Champ', 'Règle']}
|
|
429
|
+
rows={[
|
|
430
|
+
[<C>id</C>, <>« éditeur.nom » en minuscules, chiffres et tirets, ex. <C>monagence.bonjour</C>. Ne change jamais. Les préfixes <C>novalabs.</C> et <C>snapify.</C> sont réservés.</>],
|
|
431
|
+
[<C>version</C>, <>SemVer <C>x.y.z</C>. Une version supérieure est proposée comme mise à jour.</>],
|
|
432
|
+
[<C>pricing</C>, <><C>free</C> (licence gratuite incluse) ou <C>paid</C> (clé de licence à saisir).</>],
|
|
433
|
+
[<C>engines.snapify</C>, <>Version d’API requise : <C>*</C>, <C>1.3.0</C>, <C>>=1.2.0</C> ou <C>^1.3.0</C>.</>],
|
|
434
|
+
[<C>permissions</C>, <>Capacités serveur demandées, voir <Link to="/docs/reference/permissions">Permissions</Link>.</>],
|
|
435
|
+
[<C>slots</C>, <><C>floating</C> (fixé à l’écran) et/ou <C>header-actions</C> (icônes de l’en-tête, placées par le thème).</>],
|
|
436
|
+
[<C>entries</C>, <><C>public</C>, <C>server</C>, <C>admin</C> : chemins vers les sources.</>],
|
|
437
|
+
[<C>settings</C>, <>Réglages, voir <Link to="/docs/modules/reglages">Réglages</Link>.</>],
|
|
438
|
+
[<C>routes</C>, <>Adresses gérées par le module : <C>/panier/</C> (exacte) ou <C>/boutique/*</C> (avec sous-pages).</>],
|
|
439
|
+
[<C>widgets</C>, 'Widgets insérables dans une page avec le bloc « Module » de l’éditeur.'],
|
|
440
|
+
[<C>adminPages</C>, 'Pages ajoutées au menu de l’administration.'],
|
|
441
|
+
[<C>requires</C>, <>Modules indispensables et version : <C>{'{ "novalabs.shop": "^1.0.0" }'}</C>.</>],
|
|
442
|
+
[<C>uses</C>, 'Modules facultatifs dont le module utilise les services s’ils sont actifs.'],
|
|
443
|
+
[<><C>category</C>, <C>icon</C>, <C>screenshot</C></>, 'Présentation dans l’administration et sur la marketplace.'],
|
|
444
|
+
]}
|
|
445
|
+
/>
|
|
446
|
+
<H2>Icônes</H2>
|
|
447
|
+
<p>
|
|
448
|
+
<C>icon</C> accepte : <C>bot</C>, <C>calendar</C>, <C>chart-bar</C>, <C>credit-card</C>, <C>database</C>, <C>folder</C>,{' '}
|
|
449
|
+
<C>globe</C>, <C>heart</C>, <C>mail</C>, <C>message-circle</C>, <C>messages-square</C>, <C>package</C>, <C>phone</C>,{' '}
|
|
450
|
+
<C>receipt</C>, <C>search</C>, <C>shopping-bag</C>, <C>shopping-cart</C>, <C>sparkles</C>, <C>store</C>, <C>tag</C>,{' '}
|
|
451
|
+
<C>ticket</C>, <C>truck</C>, <C>user</C>, <C>users</C>. Une autre valeur affiche une icône générique.
|
|
452
|
+
</p>
|
|
453
|
+
<H2>Capture d’écran</H2>
|
|
454
|
+
<p>
|
|
455
|
+
<C>"screenshot": "screenshot.jpg"</C> intègre une image JPEG, PNG ou WebP (2 Mo maximum) au paquet. Elle sert de couverture sur la
|
|
456
|
+
marketplace et dans l’administration. Format conseillé : 1280 × 720 pour un module, 4:3 pour un thème.
|
|
457
|
+
</p>
|
|
458
|
+
<H2>Exemple complet</H2>
|
|
459
|
+
<Code lang="json" title="snapify.module.json du Calendrier (extrait)">{`
|
|
460
|
+
{
|
|
461
|
+
"id": "novalabs.calendar",
|
|
462
|
+
"name": "Calendrier",
|
|
463
|
+
"version": "1.0.1",
|
|
464
|
+
"pricing": "free",
|
|
465
|
+
"icon": "calendar",
|
|
466
|
+
"screenshot": "screenshot.jpg",
|
|
467
|
+
"engines": { "snapify": "^1.2.0" },
|
|
468
|
+
"permissions": ["http:routes", "admin:routes", "database"],
|
|
469
|
+
"slots": [],
|
|
470
|
+
"routes": ["/agenda/*", "/calendrier/*"],
|
|
471
|
+
"entries": { "public": "src/public/index.tsx", "server": "src/server.ts", "admin": "src/admin/index.tsx" },
|
|
472
|
+
"adminPages": [{ "id": "events", "label": "Événements", "icon": "calendar" }],
|
|
473
|
+
"widgets": [
|
|
474
|
+
{
|
|
475
|
+
"id": "upcoming",
|
|
476
|
+
"label": "Prochains événements",
|
|
477
|
+
"fields": [{ "key": "limit", "type": "number", "label": "Nombre d’événements", "default": 5, "min": 1, "max": 20 }]
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
`}</Code>
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function SettingsDoc() {
|
|
487
|
+
return (
|
|
488
|
+
<>
|
|
489
|
+
<p>
|
|
490
|
+
Chaque entrée de <C>settings</C> devient un champ de la page du module dans l’administration. Les valeurs sont validées côté serveur
|
|
491
|
+
avant d’être enregistrées, selon le type déclaré.
|
|
492
|
+
</p>
|
|
493
|
+
<H2>Types de champs</H2>
|
|
494
|
+
<Table
|
|
495
|
+
head={['type', 'Valeur', 'Options utiles']}
|
|
496
|
+
rows={[
|
|
497
|
+
[<C>boolean</C>, <><C>true</C> / <C>false</C></>, '—'],
|
|
498
|
+
[<><C>text</C>, <C>phone</C></>, 'chaîne', <><C>maxLength</C>, <C>placeholder</C></>],
|
|
499
|
+
[<C>textarea</C>, 'chaîne', <><C>rows</C>, <C>maxLength</C>, <C>monospace</C></>],
|
|
500
|
+
[<C>number</C>, 'nombre', <><C>min</C>, <C>max</C>, <C>step</C>, <C>slider</C></>],
|
|
501
|
+
[<C>select</C>, <>une des <C>options[].value</C></>, <C>options</C>],
|
|
502
|
+
[<C>paths</C>, <>liste d’adresses (<C>/contact/</C>, <C>/blog/*</C>)</>, 'vide = toutes les pages'],
|
|
503
|
+
]}
|
|
504
|
+
/>
|
|
505
|
+
<p>
|
|
506
|
+
Options communes : <C>key</C> (lettres, chiffres, <C>_</C>), <C>label</C>, <C>help</C>, <C>group</C> (les champs consécutifs du même
|
|
507
|
+
groupe forment une section), <C>default</C>, <C>required</C>.
|
|
508
|
+
</p>
|
|
509
|
+
<H2>Réglages publics et privés</H2>
|
|
510
|
+
<p>
|
|
511
|
+
Par défaut, un réglage reste <strong>côté serveur</strong>. Seuls les champs marqués <C>"public": true</C> sont transmis au
|
|
512
|
+
navigateur des visiteurs.
|
|
513
|
+
</p>
|
|
514
|
+
<Code lang="json">{`
|
|
515
|
+
"settings": [
|
|
516
|
+
{ "key": "title", "type": "text", "label": "Titre", "default": "Assistant", "public": true, "maxLength": 60 },
|
|
517
|
+
{ "key": "systemPrompt", "type": "textarea", "label": "Instructions", "default": "", "rows": 6, "monospace": true },
|
|
518
|
+
{ "key": "model", "type": "select", "label": "Modèle", "default": "flash",
|
|
519
|
+
"options": [{ "value": "flash", "label": "Rapide" }, { "value": "pro", "label": "Précis" }] }
|
|
520
|
+
]
|
|
521
|
+
`}</Code>
|
|
522
|
+
<Note tone="warn">
|
|
523
|
+
Ne marquez jamais <C>public</C> un prompt, un quota, un identifiant ou une clé : tout ce qui est public est lisible dans le code de la
|
|
524
|
+
page. Les clés d’API se lisent avec <C>ctx.env()</C>, pas dans les réglages.
|
|
525
|
+
</Note>
|
|
526
|
+
<H2>Lire les réglages</H2>
|
|
527
|
+
<ul>
|
|
528
|
+
<li>
|
|
529
|
+
Navigateur : <C>props.settings</C> (réglages publics uniquement).
|
|
530
|
+
</li>
|
|
531
|
+
<li>
|
|
532
|
+
Serveur : <C>ctx.settings()</C> renvoie tous les réglages, à jour à chaque appel.
|
|
533
|
+
</li>
|
|
534
|
+
<li>
|
|
535
|
+
Administration : <C>props.settings</C> d’une page d’administration, et <C>canConfigure</C> pour savoir si l’utilisateur peut les
|
|
536
|
+
modifier.
|
|
537
|
+
</li>
|
|
538
|
+
</ul>
|
|
539
|
+
</>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function PublicSide() {
|
|
544
|
+
return (
|
|
545
|
+
<>
|
|
546
|
+
<p>
|
|
547
|
+
L’entrée <C>public</C> exporte un <C>definePublicModule()</C> qui peut fournir trois types de composants : des emplacements, une page
|
|
548
|
+
et des widgets.
|
|
549
|
+
</p>
|
|
550
|
+
<H2>Props communes</H2>
|
|
551
|
+
<Table
|
|
552
|
+
head={['Prop', 'Description']}
|
|
553
|
+
rows={[
|
|
554
|
+
[<C>settings</C>, 'Réglages marqués public.'],
|
|
555
|
+
[<C>site</C>, 'Nom, raison sociale, téléphone, e-mail, adresse du site.'],
|
|
556
|
+
[<C>path</C>, 'Adresse de la page affichée.'],
|
|
557
|
+
[<C>isPreview</C>, <><C>true</C> dans l’aperçu de l’éditeur : les widgets interactifs peuvent se masquer.</>],
|
|
558
|
+
[<C>navigate(path)</C>, 'Navigation interne sans rechargement.'],
|
|
559
|
+
[<><C>api.get</C>, <C>api.post</C></>, <>Appels vers vos routes serveur <C>/api/modules/<id>/…</C></>],
|
|
560
|
+
[<C>module</C>, <>Identifiant et version du module.</>],
|
|
561
|
+
]}
|
|
562
|
+
/>
|
|
563
|
+
<H2>Emplacements</H2>
|
|
564
|
+
<p>
|
|
565
|
+
<C>floating</C> affiche un élément fixé à l’écran (bulle de chat, bouton WhatsApp). <C>header-actions</C> place des icônes dans
|
|
566
|
+
l’en-tête, à l’endroit choisi par le thème (panier, compte client).
|
|
567
|
+
</p>
|
|
568
|
+
<Code lang="tsx">{`
|
|
569
|
+
export default definePublicModule<Settings>({
|
|
570
|
+
slots: { floating: ChatBubble, 'header-actions': CartIcon },
|
|
571
|
+
});
|
|
572
|
+
`}</Code>
|
|
573
|
+
<H2>Pages</H2>
|
|
574
|
+
<p>
|
|
575
|
+
Déclarez les adresses dans <C>routes</C> du manifeste ; le composant <C>page</C> est affiché entre l’en-tête et le pied de page du
|
|
576
|
+
thème. Il reçoit en plus <C>search</C> (paramètres de l’adresse) et <C>setMeta()</C> pour le titre et la description SEO.
|
|
577
|
+
</p>
|
|
578
|
+
<Code lang="tsx">{`
|
|
579
|
+
import { definePublicModule, type PublicPageProps } from '@snapify/sdk';
|
|
580
|
+
|
|
581
|
+
function AgendaPage({ path, search, setMeta, api }: PublicPageProps<Settings>) {
|
|
582
|
+
setMeta({ title: 'Agenda', description: 'Nos prochains événements' });
|
|
583
|
+
const slug = path.replace(/^\\/agenda\\/?/, '').replace(/\\/$/, '');
|
|
584
|
+
return slug ? <EventDetail slug={slug} api={api} /> : <EventList query={search} api={api} />;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export default definePublicModule<Settings>({ page: AgendaPage });
|
|
588
|
+
`}</Code>
|
|
589
|
+
<H2>Widgets</H2>
|
|
590
|
+
<p>
|
|
591
|
+
Un widget est inséré dans n’importe quelle page avec le bloc <strong>Module</strong> de l’éditeur. Ses options (<C>fields</C> du
|
|
592
|
+
manifeste) sont saisies dans l’éditeur et reçues dans <C>options</C>, complétées par les valeurs par défaut.
|
|
593
|
+
</p>
|
|
594
|
+
<Code lang="tsx">{`
|
|
595
|
+
import { definePublicModule, type WidgetProps } from '@snapify/sdk';
|
|
596
|
+
|
|
597
|
+
function Upcoming({ options, api, navigate }: WidgetProps<Settings, { title: string; limit: number }>) {
|
|
598
|
+
// …charger /events?limit=options.limit puis afficher la liste
|
|
599
|
+
return <section><h2>{options.title}</h2>…</section>;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export default definePublicModule<Settings>({ widgets: { upcoming: Upcoming } });
|
|
603
|
+
`}</Code>
|
|
604
|
+
<H2>Styles</H2>
|
|
605
|
+
<p>
|
|
606
|
+
Tailwind est compilé avec le module : seules les classes utilisées sont incluses. Les couleurs du site sont disponibles via les classes{' '}
|
|
607
|
+
<C>px-*</C> (<C>bg-px-primary</C>, <C>text-px-navy</C>…), ce qui aligne le module sur l’identité visuelle choisie par le client.
|
|
608
|
+
</p>
|
|
609
|
+
<ul>
|
|
610
|
+
<li>
|
|
611
|
+
La feuille de style est limitée au conteneur du module (<C>[data-snapify-scope="<id>"]</C>) : elle ne peut pas modifier le
|
|
612
|
+
thème ni les autres modules.
|
|
613
|
+
</li>
|
|
614
|
+
<li>React est fourni par le site ; il n’est jamais embarqué dans votre bundle.</li>
|
|
615
|
+
<li>Une erreur dans un module est isolée : elle n’affecte ni la page ni les autres modules.</li>
|
|
616
|
+
</ul>
|
|
617
|
+
<H2>Utilitaires</H2>
|
|
618
|
+
<ul>
|
|
619
|
+
<li>
|
|
620
|
+
<C>matchesPaths(patterns, path)</C> : liste vide = toutes les pages ; <C>/blog/*</C> inclut les sous-pages.
|
|
621
|
+
</li>
|
|
622
|
+
<li>
|
|
623
|
+
<C>createModuleApi(id)</C> : client des routes publiques d’un autre module actif.
|
|
624
|
+
</li>
|
|
625
|
+
<li>
|
|
626
|
+
<C>isModuleActive(id)</C> : savoir si un module est actif sur le site.
|
|
627
|
+
</li>
|
|
628
|
+
</ul>
|
|
629
|
+
</>
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function ServerSide() {
|
|
634
|
+
return (
|
|
635
|
+
<>
|
|
636
|
+
<p>
|
|
637
|
+
L’entrée <C>server</C> exporte un <C>defineServerModule()</C>. Sa fonction <C>activate(ctx)</C> est appelée à l’activation ; la
|
|
638
|
+
fonction qu’elle renvoie éventuellement est appelée à la désactivation.
|
|
639
|
+
</p>
|
|
640
|
+
<Code lang="ts">{`
|
|
641
|
+
import { defineServerModule, type ServerContext } from '@snapify/sdk';
|
|
642
|
+
|
|
643
|
+
interface Settings { greeting: string }
|
|
644
|
+
|
|
645
|
+
export default defineServerModule<Settings>({
|
|
646
|
+
activate(ctx: ServerContext<Settings>) {
|
|
647
|
+
ctx.http.post('/hello', async (req) => {
|
|
648
|
+
const { name } = (req.body ?? {}) as { name?: string };
|
|
649
|
+
if (typeof name !== 'string' || !name.trim()) throw ctx.error(400, 'Nom manquant');
|
|
650
|
+
return { message: ctx.settings().greeting + ' ' + name.slice(0, 50) };
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const timer = setInterval(() => ctx.log.info('toujours actif'), 3_600_000);
|
|
654
|
+
return () => clearInterval(timer);
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
`}</Code>
|
|
658
|
+
<H2>Le contexte ctx</H2>
|
|
659
|
+
<p>Chaque capacité est liée à une permission du manifeste ; sans elle, l’appel lève une erreur.</p>
|
|
660
|
+
<Table
|
|
661
|
+
head={['API', 'Permission', 'Rôle']}
|
|
662
|
+
rows={[
|
|
663
|
+
[<><C>ctx.http.get/post(route, handler)</C></>, <C>http:routes</C>, <>Routes publiques sous <C>/api/modules/<id>/…</C>, segments <C>:nom</C> acceptés.</>],
|
|
664
|
+
[<C>ctx.http.reply(body, opts)</C>, <C>http:cookies</C>, <>Choisir le statut et déposer des cookies <C>sm_*</C>.</>],
|
|
665
|
+
[<C>ctx.db.collection(nom)</C>, <C>database</C>, <>Collections d’enregistrements, voir <Link to="/docs/modules/base-de-donnees">Base de données</Link>.</>],
|
|
666
|
+
[<C>ctx.storage.get/set/delete</C>, <C>storage</C>, 'Valeurs JSON clé/valeur (64 Ko max.).'],
|
|
667
|
+
[<C>ctx.files.saveImage/delete</C>, <C>files</C>, <>Images publiques sous <C>/uploads/modules/<id>/</C> (2 Mo max.).</>],
|
|
668
|
+
[<C>ctx.mail.send/render</C>, <C>mail:send</C>, 'E-mails via le SMTP du site, gabarit aux couleurs du site.'],
|
|
669
|
+
[<C>ctx.site()</C>, <C>site:read</C>, 'Coordonnées, couleurs et URL publique du site.'],
|
|
670
|
+
[<C>ctx.env(NOM)</C>, <C>env:NOM</C>, 'Une variable d’environnement précise (clé d’API…).'],
|
|
671
|
+
[<C>ctx.fetch(url, init)</C>, <C>net:hôte</C>, 'Requête HTTPS vers cet hôte uniquement, 30 s max.'],
|
|
672
|
+
[<C>ctx.admin.get/post/put/delete</C>, <C>admin:routes</C>, <>Routes réservées à l’administration (<C>/api/admin/modules/<id>/api/…</C>).</>],
|
|
673
|
+
[<><C>ctx.settings()</C>, <C>ctx.log</C>, <C>ctx.error()</C>, <C>ctx.lock()</C>, <C>ctx.admin.status()</C>, <C>ctx.provide/use</C></>, '—', 'Toujours disponibles.'],
|
|
674
|
+
]}
|
|
675
|
+
/>
|
|
676
|
+
<H2>Requêtes et réponses</H2>
|
|
677
|
+
<p>
|
|
678
|
+
Le handler reçoit <C>req</C> : <C>method</C>, <C>path</C>, <C>params</C>, <C>query</C>, <C>body</C>, <C>ip</C>, <C>userAgent</C>,{' '}
|
|
679
|
+
<C>cookies</C> (avec <C>http:cookies</C>) et <C>admin</C> (utilisateur connecté, routes d’administration). La valeur renvoyée est
|
|
680
|
+
sérialisée en JSON.
|
|
681
|
+
</p>
|
|
682
|
+
<Code lang="ts">{`
|
|
683
|
+
ctx.http.post('/login', async (req) => {
|
|
684
|
+
const session = await createSession(req.body);
|
|
685
|
+
return ctx.http.reply({ ok: true }, {
|
|
686
|
+
status: 201,
|
|
687
|
+
cookies: [{ name: 'sm_session', value: session.token, maxAgeSec: 30 * 24 * 3600 }],
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
`}</Code>
|
|
691
|
+
<ul>
|
|
692
|
+
<li>
|
|
693
|
+
<C>throw ctx.error(422, 'Message')</C> renvoie ce statut et ce message au visiteur. Toute autre exception renvoie une erreur 500
|
|
694
|
+
générique et est journalisée côté serveur.
|
|
695
|
+
</li>
|
|
696
|
+
<li>Les routes publiques sont limitées à 120 requêtes par minute et par adresse IP ; un handler est interrompu après 60 s.</li>
|
|
697
|
+
<li>
|
|
698
|
+
Les cookies sont toujours <C>HttpOnly</C>, <C>SameSite=Lax</C>, <C>Path=/</C> et <C>Secure</C> en HTTPS ; leur nom doit commencer
|
|
699
|
+
par <C>sm_</C>.
|
|
700
|
+
</li>
|
|
701
|
+
</ul>
|
|
702
|
+
<H2>E-mails</H2>
|
|
703
|
+
<Code lang="ts">{`
|
|
704
|
+
const { html, text } = ctx.mail.render({
|
|
705
|
+
title: 'Commande confirmée',
|
|
706
|
+
intro: 'Merci pour votre commande n° 1042.',
|
|
707
|
+
lines: ['Livraison sous 48 h'],
|
|
708
|
+
button: { label: 'Suivre ma commande', url: ctx.site().publicUrl + '/compte/' },
|
|
709
|
+
});
|
|
710
|
+
const { sent, reason } = await ctx.mail.send({ to: client.email, subject: 'Commande confirmée', html, text });
|
|
711
|
+
`}</Code>
|
|
712
|
+
<p>
|
|
713
|
+
<C>send()</C> n’échoue jamais : vérifiez <C>sent</C> (par exemple si le SMTP du site n’est pas configuré).
|
|
714
|
+
</p>
|
|
715
|
+
<H2>Sections critiques</H2>
|
|
716
|
+
<p>
|
|
717
|
+
<C>ctx.lock(clé, fn)</C> exécute <C>fn</C> sans concurrence pour une même clé : décrément de stock, numérotation de commandes…
|
|
718
|
+
</p>
|
|
719
|
+
<Code lang="ts">{`
|
|
720
|
+
const number = await ctx.lock('order-number', async () => {
|
|
721
|
+
const next = ((await ctx.storage.get<number>('lastOrder')) ?? 1000) + 1;
|
|
722
|
+
await ctx.storage.set('lastOrder', next);
|
|
723
|
+
return next;
|
|
724
|
+
});
|
|
725
|
+
`}</Code>
|
|
726
|
+
</>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function Database() {
|
|
731
|
+
return (
|
|
732
|
+
<>
|
|
733
|
+
<p>
|
|
734
|
+
Avec la permission <C>database</C>, un module dispose de collections d’enregistrements JSON qui lui sont propres. Elles sont supprimées
|
|
735
|
+
à la désinstallation.
|
|
736
|
+
</p>
|
|
737
|
+
<Code lang="ts">{`
|
|
738
|
+
interface CalEvent { title: string; date: string; status: 'draft' | 'published' }
|
|
739
|
+
|
|
740
|
+
const events = ctx.db.collection<CalEvent>('events');
|
|
741
|
+
|
|
742
|
+
const created = await events.insert({ title: 'Atelier', date: '2026-10-17', status: 'published' });
|
|
743
|
+
await events.update(created.id, { title: 'Atelier découverte' });
|
|
744
|
+
|
|
745
|
+
const { items, total } = await events.find({
|
|
746
|
+
where: { status: 'published' },
|
|
747
|
+
filters: [{ field: 'date', op: 'gte', value: '2026-10-01' }],
|
|
748
|
+
search: { fields: ['title'], text: 'atelier' },
|
|
749
|
+
orderBy: [{ field: 'date', dir: 'asc' }],
|
|
750
|
+
limit: 20,
|
|
751
|
+
offset: 0,
|
|
752
|
+
});
|
|
753
|
+
`}</Code>
|
|
754
|
+
<H2>Méthodes</H2>
|
|
755
|
+
<Table
|
|
756
|
+
head={['Méthode', 'Rôle']}
|
|
757
|
+
rows={[
|
|
758
|
+
[<C>get(id)</C>, <>Un enregistrement ou <C>null</C>.</>],
|
|
759
|
+
[<C>getMany(ids)</C>, 'Plusieurs enregistrements.'],
|
|
760
|
+
[<C>find(query)</C>, <><C>{'{ items, total }'}</C> selon les filtres.</>],
|
|
761
|
+
[<C>findOne(where)</C>, 'Premier enregistrement correspondant.'],
|
|
762
|
+
[<C>count(query)</C>, 'Nombre d’enregistrements.'],
|
|
763
|
+
[<C>insert(data, {'{ id }'})</C>, 'Crée un enregistrement (identifiant aléatoire si absent).'],
|
|
764
|
+
[<C>update(id, patch)</C>, <>Fusion superficielle ; <C>null</C> si absent.</>],
|
|
765
|
+
[<C>delete(id)</C>, <><C>true</C> si supprimé.</>],
|
|
766
|
+
]}
|
|
767
|
+
/>
|
|
768
|
+
<p>
|
|
769
|
+
Chaque enregistrement reçoit <C>id</C>, <C>createdAt</C> et <C>updatedAt</C>. Taille maximale : 256 Ko par enregistrement.
|
|
770
|
+
</p>
|
|
771
|
+
<H2>Requêtes</H2>
|
|
772
|
+
<ul>
|
|
773
|
+
<li>
|
|
774
|
+
<C>where</C> : égalités simples sur des champs de premier niveau (un tableau doit contenir la valeur donnée).
|
|
775
|
+
</li>
|
|
776
|
+
<li>
|
|
777
|
+
<C>filters</C> : opérateurs <C>eq</C>, <C>ne</C>, <C>gt</C>, <C>gte</C>, <C>lt</C>, <C>lte</C>, <C>in</C>, <C>contains</C>,{' '}
|
|
778
|
+
<C>exists</C>.
|
|
779
|
+
</li>
|
|
780
|
+
<li>
|
|
781
|
+
<C>search</C> : recherche insensible à la casse et aux accents dans des champs texte.
|
|
782
|
+
</li>
|
|
783
|
+
<li>
|
|
784
|
+
<C>orderBy</C> : plusieurs critères, <C>numeric: true</C> pour trier des nombres.
|
|
785
|
+
</li>
|
|
786
|
+
<li>
|
|
787
|
+
<C>limit</C> : 100 par défaut, 500 au maximum.
|
|
788
|
+
</li>
|
|
789
|
+
</ul>
|
|
790
|
+
<Note tone="tip">Pour un simple compteur ou un paramètre interne, préférez <C>ctx.storage</C> (permission <C>storage</C>).</Note>
|
|
791
|
+
</>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function Admin() {
|
|
796
|
+
return (
|
|
797
|
+
<>
|
|
798
|
+
<p>
|
|
799
|
+
Un module peut ajouter des pages au menu de l’administration (liste d’événements, commandes, clients…). Elles utilisent les composants
|
|
800
|
+
du back-office : elles ont exactement la même apparence que le reste de l’administration.
|
|
801
|
+
</p>
|
|
802
|
+
<H2>Déclarer les pages</H2>
|
|
803
|
+
<Code lang="json" title="snapify.module.json">{`
|
|
804
|
+
"permissions": ["admin:routes", "database"],
|
|
805
|
+
"entries": { "public": "src/public/index.tsx", "server": "src/server.ts", "admin": "src/admin/index.tsx" },
|
|
806
|
+
"adminPages": [{ "id": "events", "label": "Événements", "icon": "calendar" }]
|
|
807
|
+
`}</Code>
|
|
808
|
+
<Code lang="tsx" title="src/admin/index.tsx">{`
|
|
809
|
+
import { defineAdminModule } from '@snapify/sdk/admin';
|
|
810
|
+
import { EventsPage } from './EventsPage';
|
|
811
|
+
|
|
812
|
+
export default defineAdminModule({ pages: { events: EventsPage } });
|
|
813
|
+
`}</Code>
|
|
814
|
+
<p>
|
|
815
|
+
La page est servie à <C>/administrator/m/<id>/<page></C>. Elle reçoit <C>api</C> (routes d’administration),{' '}
|
|
816
|
+
<C>subpath</C> et <C>navigate()</C> pour ses sous-pages, <C>settings</C>, <C>user</C> et <C>canConfigure</C>.
|
|
817
|
+
</p>
|
|
818
|
+
<H2>Composants du back-office</H2>
|
|
819
|
+
<Code lang="tsx" title="src/admin/EventsPage.tsx">{`
|
|
820
|
+
import { CalendarDays } from 'lucide-react';
|
|
821
|
+
import { Button, Card, EmptyState, PageHeader, Table, toast, useAsync, type AdminPageProps } from '@snapify/sdk/admin';
|
|
822
|
+
|
|
823
|
+
export function EventsPage({ api }: AdminPageProps) {
|
|
824
|
+
const { data, reload } = useAsync(() => api.get<{ items: Event[] }>('/events'), []);
|
|
825
|
+
const remove = async (id: string) => {
|
|
826
|
+
await api.delete('/events/' + id);
|
|
827
|
+
toast.success('Événement supprimé');
|
|
828
|
+
reload();
|
|
829
|
+
};
|
|
830
|
+
return (
|
|
831
|
+
<>
|
|
832
|
+
<PageHeader title="Événements" actions={<Button>Nouvel événement</Button>} />
|
|
833
|
+
<Card>
|
|
834
|
+
{data?.items.length ? <Table>…</Table> : <EmptyState icon={CalendarDays} title="Aucun événement" />}
|
|
835
|
+
</Card>
|
|
836
|
+
</>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
`}</Code>
|
|
840
|
+
<p>Composants disponibles :</p>
|
|
841
|
+
<p className="text-[14px] leading-7">
|
|
842
|
+
{[
|
|
843
|
+
'Alert', 'Badge', 'Button', 'Card', 'Drawer', 'Dropdown', 'EmptyState', 'ErrorState', 'Field', 'ImageField', 'Input', 'MediaPicker',
|
|
844
|
+
'Modal', 'PageHeader', 'PageSkeleton', 'Pagination', 'RichTextEditor', 'Section', 'Segmented', 'Select', 'Skeleton', 'Spinner',
|
|
845
|
+
'StatCard', 'Switch', 'Table', 'Tabs', 'TagInput', 'Td', 'Textarea', 'Th',
|
|
846
|
+
].map((n, i) => (
|
|
847
|
+
<span key={n}>
|
|
848
|
+
{i > 0 && ' '}
|
|
849
|
+
<C>{n}</C>
|
|
850
|
+
</span>
|
|
851
|
+
))}
|
|
852
|
+
</p>
|
|
853
|
+
<p>
|
|
854
|
+
Outils : <C>toast</C>, <C>useAsync</C>, <C>useConfirm</C>, <C>useDebounced</C>, <C>usePagination</C>, <C>usePaging</C>,{' '}
|
|
855
|
+
<C>errorMessage</C>, <C>fmtDate</C>, <C>fmtDateTime</C>, <C>fmtNumber</C>, <C>timeAgo</C>, <C>slugify</C>, <C>downloadCsv</C>, <C>cx</C>.
|
|
856
|
+
</p>
|
|
857
|
+
<Note tone="warn" title="Deux pièges fréquents">
|
|
858
|
+
<C>Input</C> et <C>Textarea</C> sont des champs natifs : leur <C>onChange</C> reçoit un événement (lisez{' '}
|
|
859
|
+
<C>e.target.value</C>), alors que <C>Select</C>, <C>Switch</C>, <C>Segmented</C> et <C>TagInput</C> reçoivent directement la valeur.
|
|
860
|
+
Et une <C>Modal</C> ne s’affiche qu’avec <C>open</C>.
|
|
861
|
+
</Note>
|
|
862
|
+
<H2>Routes d’administration</H2>
|
|
863
|
+
<Code lang="ts">{`
|
|
864
|
+
ctx.admin.get('/events', async () => ({ items: (await events.find({ limit: 500 })).items }));
|
|
865
|
+
ctx.admin.delete('/events/:id', async (req) => ({ ok: await events.delete(req.params.id) }));
|
|
866
|
+
`}</Code>
|
|
867
|
+
<p>
|
|
868
|
+
Elles ne sont accessibles qu’aux utilisateurs autorisés à gérer les données des modules ; <C>req.admin</C> contient l’utilisateur
|
|
869
|
+
connecté.
|
|
870
|
+
</p>
|
|
871
|
+
<H2>Statistiques et alertes</H2>
|
|
872
|
+
<p>
|
|
873
|
+
<C>ctx.admin.status()</C> alimente les indicateurs affichés en haut de la page du module, sans permission particulière :
|
|
874
|
+
</p>
|
|
875
|
+
<Code lang="ts">{`
|
|
876
|
+
ctx.admin.status(async () => {
|
|
877
|
+
const drafts = await events.count({ where: { status: 'draft' } });
|
|
878
|
+
return {
|
|
879
|
+
stats: [{ label: 'Événements', value: await events.count() }],
|
|
880
|
+
alerts: drafts ? [{ tone: 'info', text: drafts + ' brouillon(s) en attente de publication.' }] : [],
|
|
881
|
+
};
|
|
882
|
+
});
|
|
883
|
+
`}</Code>
|
|
884
|
+
</>
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function Dependencies() {
|
|
889
|
+
return (
|
|
890
|
+
<>
|
|
891
|
+
<p>
|
|
892
|
+
Les modules peuvent coopérer : le Panier s’appuie sur la Boutique, la Commande sur le Panier, l’Espace client utilise la Commande s’il
|
|
893
|
+
est installé.
|
|
894
|
+
</p>
|
|
895
|
+
<Table
|
|
896
|
+
head={['Champ', 'Effet']}
|
|
897
|
+
rows={[
|
|
898
|
+
[<C>requires</C>, 'Modules indispensables avec leur version. Si l’un manque ou n’est pas compatible, le module reste bloqué et l’administration indique pourquoi.'],
|
|
899
|
+
[<C>uses</C>, 'Modules facultatifs : leurs services sont utilisés s’ils sont actifs.'],
|
|
900
|
+
]}
|
|
901
|
+
/>
|
|
902
|
+
<Code lang="json">{`
|
|
903
|
+
"requires": { "novalabs.shop": "^1.0.0", "novalabs.cart": "^1.0.0" },
|
|
904
|
+
"uses": ["novalabs.customers"]
|
|
905
|
+
`}</Code>
|
|
906
|
+
<H2>Publier et utiliser des services</H2>
|
|
907
|
+
<Code lang="ts" title="module fournisseur">{`
|
|
908
|
+
ctx.provide({
|
|
909
|
+
async getProduct(id: string) { return products.get(id); },
|
|
910
|
+
async reserveStock(id: string, qty: number) { /* … */ },
|
|
911
|
+
});
|
|
912
|
+
`}</Code>
|
|
913
|
+
<Code lang="ts" title="module consommateur">{`
|
|
914
|
+
interface ShopServices { getProduct(id: string): Promise<Product | null> }
|
|
915
|
+
|
|
916
|
+
const shop = ctx.use<ShopServices>('novalabs.shop');
|
|
917
|
+
const customers = ctx.use<CustomerServices>('novalabs.customers'); // null si inactif
|
|
918
|
+
`}</Code>
|
|
919
|
+
<p>
|
|
920
|
+
Côté navigateur, <C>createModuleApi('novalabs.shop')</C> appelle les routes publiques d’un autre module, et <C>@snapify/sdk/commerce</C>{' '}
|
|
921
|
+
partage l’état du panier et du client entre les modules e-commerce.
|
|
922
|
+
</p>
|
|
923
|
+
</>
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/* ------------------------------------------------------------------ Thèmes */
|
|
928
|
+
|
|
929
|
+
function ThemeCreate() {
|
|
930
|
+
return (
|
|
931
|
+
<>
|
|
932
|
+
<p>
|
|
933
|
+
Un thème fournit toute la présentation : en-tête, pied de page, rendu des blocs, mise en page des pages et des articles, page 404. Le
|
|
934
|
+
contenu (pages, menus, articles, identité visuelle) ne dépend pas du thème : changer de thème ne modifie aucune donnée.
|
|
935
|
+
</p>
|
|
936
|
+
<H2>Créer le projet</H2>
|
|
937
|
+
<Code lang="bash">{`
|
|
938
|
+
npm create snapify@latest mon-theme -- --theme --id monagence.mon-theme
|
|
939
|
+
cd mon-theme
|
|
940
|
+
npm install
|
|
941
|
+
npm run build
|
|
942
|
+
`}</Code>
|
|
943
|
+
<p>
|
|
944
|
+
Le modèle est un thème complet et fonctionnel : en-tête avec menu mobile, pied de page, cinq blocs, page article, page 404 et trois
|
|
945
|
+
options. Il passe l’analyse marketplace dès sa création : remplacez-le progressivement par votre design.
|
|
946
|
+
</p>
|
|
947
|
+
<Code lang="text" title="structure">{`
|
|
948
|
+
mon-theme/
|
|
949
|
+
├── package.json # scripts build et typecheck, SDK en dépendance
|
|
950
|
+
├── tsconfig.json
|
|
951
|
+
├── snapify.theme.json # manifeste (mêmes champs qu’un module, sans permissions ni slots)
|
|
952
|
+
├── README.md # inclus dans le paquet
|
|
953
|
+
└── src/
|
|
954
|
+
├── index.tsx # export default defineTheme({...})
|
|
955
|
+
├── Header.tsx, Footer.tsx
|
|
956
|
+
├── blocks.tsx # rendu des blocs de l’éditeur
|
|
957
|
+
├── Views.tsx # article, page 404
|
|
958
|
+
├── site.tsx # options, menus, liens, textes à variables
|
|
959
|
+
└── styles.css # importé depuis l’entrée ; Tailwind est disponible
|
|
960
|
+
`}</Code>
|
|
961
|
+
<p>
|
|
962
|
+
Ajoutez une capture d’écran (<C>screenshot.jpg</C>, format 4:3, JPEG, PNG ou WebP, 2 Mo max.) à la racine et déclarez-la dans le
|
|
963
|
+
manifeste avec <C>"screenshot": "screenshot.jpg"</C>.
|
|
964
|
+
</p>
|
|
965
|
+
<H2>Règles</H2>
|
|
966
|
+
<ul>
|
|
967
|
+
<li>
|
|
968
|
+
<C>entries.public</C> est obligatoire ; <C>entries.server</C>, <C>permissions</C> et <C>slots</C> sont interdits.
|
|
969
|
+
</li>
|
|
970
|
+
<li>
|
|
971
|
+
Les <C>settings</C> deviennent les <strong>options du thème</strong>, modifiables dans <em>Apparence › Personnaliser</em> avec
|
|
972
|
+
aperçu en direct. Elles sont toutes transmises au navigateur.
|
|
973
|
+
</li>
|
|
974
|
+
</ul>
|
|
975
|
+
<H2>Le contrat defineTheme</H2>
|
|
976
|
+
<Code lang="tsx" title="src/index.tsx">{`
|
|
977
|
+
import { defineTheme, ModuleSlot, useSnapify, type BlockProps } from '@snapify/sdk/theme';
|
|
978
|
+
import './styles.css';
|
|
979
|
+
|
|
980
|
+
function Header({ path, navigate }) {
|
|
981
|
+
const { site, settings } = useSnapify<{ sticky: boolean }>();
|
|
982
|
+
return (
|
|
983
|
+
<header className={settings.sticky ? 'sticky top-0' : ''}>
|
|
984
|
+
<a onClick={() => navigate('/')}>{site.branding.siteName}</a>
|
|
985
|
+
{/* menus : site.menus … */}
|
|
986
|
+
<ModuleSlot name="header-actions" />
|
|
987
|
+
</header>
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export default defineTheme({
|
|
992
|
+
Header,
|
|
993
|
+
Footer: ({ navigate, openCookies }) => <footer>…</footer>,
|
|
994
|
+
Layout: ({ children }) => <div className="theme-root">{children}</div>, // facultatif
|
|
995
|
+
Page: ({ page, children }) => <main>{children}</main>, // facultatif
|
|
996
|
+
Post: ({ post, children }) => <article>{children}</article>, // facultatif
|
|
997
|
+
NotFound: ({ navigate }) => <p>Page introuvable</p>, // facultatif
|
|
998
|
+
blocks: {
|
|
999
|
+
hero: ({ d, style }: BlockProps<'hero'>) => <section>{d.title}</section>,
|
|
1000
|
+
// un bloc sans rendu n’est pas affiché ; l’aperçu de l’éditeur le signale
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
`}</Code>
|
|
1004
|
+
<H2>useSnapify()</H2>
|
|
1005
|
+
<Table
|
|
1006
|
+
head={['Valeur', 'Contenu']}
|
|
1007
|
+
rows={[
|
|
1008
|
+
[<C>site</C>, 'Configuration publique : menus, identité visuelle, réglages généraux.'],
|
|
1009
|
+
[<C>info</C>, 'Coordonnées du site.'],
|
|
1010
|
+
[<C>tokens</C>, <>Valeurs des variables <C>{'{{telephone}}'}</C>, <C>{'{{email}}'}</C>… (voir <C>applyTokens()</C>).</>],
|
|
1011
|
+
[<C>settings</C>, 'Options du thème.'],
|
|
1012
|
+
[<><C>path</C>, <C>navigate</C>, <C>openCookies</C></>, 'Adresse, navigation, bannière cookies.'],
|
|
1013
|
+
[<C>isPreview</C>, 'Aperçu de l’administration : aucune action réelle (formulaires, liens externes).'],
|
|
1014
|
+
]}
|
|
1015
|
+
/>
|
|
1016
|
+
<H2>Modules dans le thème</H2>
|
|
1017
|
+
<p>
|
|
1018
|
+
Placez <C><ModuleSlot name="header-actions" /></C> dans l’en-tête pour afficher les icônes des modules (panier, compte).{' '}
|
|
1019
|
+
<C>hasModuleSlot()</C> et <C>isModuleActive()</C> permettent d’adapter la mise en page.
|
|
1020
|
+
</p>
|
|
1021
|
+
<H2>Fonctionnement</H2>
|
|
1022
|
+
<ul>
|
|
1023
|
+
<li>Un seul thème est actif. Un thème installé n’est pas activé automatiquement s’il en existe déjà un actif.</li>
|
|
1024
|
+
<li>La feuille de style et le script du thème actif sont inclus dans le HTML : pas de flash sans style.</li>
|
|
1025
|
+
<li>Un thème inactif peut être prévisualisé et réglé avant activation.</li>
|
|
1026
|
+
<li>Chaque partie est isolée : une erreur dans un bloc n’affecte pas le reste de la page.</li>
|
|
1027
|
+
<li>
|
|
1028
|
+
Si le thème actif est bloqué (signature, licence…), le site s’affiche avec un rendu minimal en attendant une réparation.
|
|
1029
|
+
</li>
|
|
1030
|
+
</ul>
|
|
1031
|
+
<Note tone="tip" title="Partir d’un exemple">
|
|
1032
|
+
Les thèmes officiels <a href={product('novalabs.sidibou')}>SidiBou</a>, <a href={product('novalabs.mahdia')}>Mahdia</a> et{' '}
|
|
1033
|
+
<a href={product('novalabs.dogga')}>Dogga</a> implémentent tout le contrat, y compris l’e-commerce.
|
|
1034
|
+
</Note>
|
|
1035
|
+
</>
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const BLOCKS: [string, string][] = [
|
|
1040
|
+
['hero', 'Bandeau principal'],
|
|
1041
|
+
['pageHeader', 'En-tête de page'],
|
|
1042
|
+
['richText', 'Texte riche'],
|
|
1043
|
+
['textMedia', 'Texte et image'],
|
|
1044
|
+
['image', 'Image'],
|
|
1045
|
+
['video', 'Vidéo'],
|
|
1046
|
+
['slides', 'Diaporama'],
|
|
1047
|
+
['cards', 'Cartes'],
|
|
1048
|
+
['showcase', 'Vitrine'],
|
|
1049
|
+
['steps', 'Étapes'],
|
|
1050
|
+
['stats', 'Chiffres clés'],
|
|
1051
|
+
['cta', 'Appel à l’action'],
|
|
1052
|
+
['panels', 'Panneaux'],
|
|
1053
|
+
['gallery', 'Galerie'],
|
|
1054
|
+
['checklist', 'Liste à cocher'],
|
|
1055
|
+
['faq', 'FAQ'],
|
|
1056
|
+
['marquee', 'Défilement'],
|
|
1057
|
+
['projects', 'Réalisations'],
|
|
1058
|
+
['table', 'Tableau'],
|
|
1059
|
+
['simulator', 'Simulateur'],
|
|
1060
|
+
['contactForm', 'Formulaire de contact'],
|
|
1061
|
+
['details', 'Détails'],
|
|
1062
|
+
['testimonials', 'Témoignages'],
|
|
1063
|
+
['spacer', 'Espacement'],
|
|
1064
|
+
['html', 'HTML personnalisé'],
|
|
1065
|
+
['module', 'Widget d’un module'],
|
|
1066
|
+
];
|
|
1067
|
+
|
|
1068
|
+
function ThemeBlocks() {
|
|
1069
|
+
return (
|
|
1070
|
+
<>
|
|
1071
|
+
<p>
|
|
1072
|
+
Les pages sont composées de blocs dans l’éditeur. Le thème fournit le rendu de chaque type dans <C>blocks</C> ; chaque composant
|
|
1073
|
+
reçoit <C>d</C> (données du bloc, complétées par les valeurs par défaut) et <C>style</C> (espacements, fond, alignement).
|
|
1074
|
+
</p>
|
|
1075
|
+
<Table head={['Type', 'Bloc']} rows={BLOCKS.map(([k, label]) => [<C>{k}</C>, label])} />
|
|
1076
|
+
<H2>Typage</H2>
|
|
1077
|
+
<Code lang="tsx">{`
|
|
1078
|
+
import type { BlockProps } from '@snapify/sdk/theme';
|
|
1079
|
+
import { applyTokens } from '@snapify/sdk/content';
|
|
1080
|
+
|
|
1081
|
+
export function Cta({ d, style }: BlockProps<'cta'>) {
|
|
1082
|
+
const { tokens } = useSnapify();
|
|
1083
|
+
return (
|
|
1084
|
+
<section data-bg={style.background}>
|
|
1085
|
+
<h2>{applyTokens(d.title, tokens)}</h2>
|
|
1086
|
+
</section>
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
`}</Code>
|
|
1090
|
+
<Note>
|
|
1091
|
+
Le bloc <C>module</C> affiche le widget d’un module : le site hôte s’occupe du rendu. Un thème peut se contenter de l’envelopper dans
|
|
1092
|
+
sa mise en page.
|
|
1093
|
+
</Note>
|
|
1094
|
+
</>
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/* ------------------------------------------------------------------ Distribution */
|
|
1099
|
+
|
|
1100
|
+
function Testing() {
|
|
1101
|
+
return (
|
|
1102
|
+
<>
|
|
1103
|
+
<p>
|
|
1104
|
+
Une extension se teste à trois niveaux : sur votre poste avec le kit, sur un site Snapify de test, puis par NOVALABS pendant la revue
|
|
1105
|
+
marketplace.
|
|
1106
|
+
</p>
|
|
1107
|
+
|
|
1108
|
+
<H2>1. Sur votre poste</H2>
|
|
1109
|
+
<Code lang="bash">{`
|
|
1110
|
+
npm run typecheck # erreurs TypeScript
|
|
1111
|
+
npm run build # compilation, puis analyse marketplace
|
|
1112
|
+
`}</Code>
|
|
1113
|
+
<p>
|
|
1114
|
+
<C>npm run build</C> rejoue <strong>exactement l’analyse de la marketplace</strong> : format du paquet, manifeste, code, permissions,
|
|
1115
|
+
sécurité et présentation. Les erreurs (<C>✖</C>) bloqueraient l’envoi ; les avertissements (<C>!</C>) sont signalés au relecteur.
|
|
1116
|
+
Deux avertissements sont normaux pendant le développement : le paquet n’est pas signé (NOVALABS le signe à la validation) et la capture
|
|
1117
|
+
d’écran manque tant que vous ne l’avez pas ajoutée.
|
|
1118
|
+
</p>
|
|
1119
|
+
<p>
|
|
1120
|
+
Pour analyser à nouveau un paquet sans recompiler : <C>npx snapify check dist/<id>-<version>.snapmod</C>. Le détail de chaque
|
|
1121
|
+
contrôle est décrit dans <a href="/docs/securite#analyse-automatique">l’analyse automatique</a>.
|
|
1122
|
+
</p>
|
|
1123
|
+
|
|
1124
|
+
<H2>2. Sur un site Snapify de test</H2>
|
|
1125
|
+
<p>
|
|
1126
|
+
Les paquets non signés ne s’installent que sur un site en <strong>mode développeur</strong>. Il vous faut une installation Snapify que
|
|
1127
|
+
vous administrez (par exemple une copie de test de votre licence Snapify), jamais le site d’un client en production.
|
|
1128
|
+
</p>
|
|
1129
|
+
<Steps
|
|
1130
|
+
items={[
|
|
1131
|
+
{
|
|
1132
|
+
title: 'Activer le mode développeur',
|
|
1133
|
+
body: (
|
|
1134
|
+
<>
|
|
1135
|
+
Ajoutez <C>SNAPIFY_MODULES_DEV_MODE=true</C> au fichier <C>.env</C> du site, puis redémarrez-le. L’administration affiche alors
|
|
1136
|
+
l’alerte « Mode développeur actif ».
|
|
1137
|
+
</>
|
|
1138
|
+
),
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
title: 'Importer le paquet',
|
|
1142
|
+
body: (
|
|
1143
|
+
<>
|
|
1144
|
+
Module : <strong>Administration › Modules › Importer un module</strong>. Thème : <strong>Apparence › Thèmes › Importer un thème</strong>.
|
|
1145
|
+
L’écran d’installation affiche les permissions demandées et l’état « non signé, mode développeur ».
|
|
1146
|
+
</>
|
|
1147
|
+
),
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
title: 'Activer et régler',
|
|
1151
|
+
body: 'Activez le module (ou prévisualisez le thème avant de l’activer), puis remplissez ses réglages.',
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
title: 'Itérer',
|
|
1155
|
+
body: (
|
|
1156
|
+
<>
|
|
1157
|
+
Après chaque <C>npm run build</C>, importez à nouveau le fichier : à version égale, l’administration propose de le{' '}
|
|
1158
|
+
<strong>réinstaller</strong> en conservant ses réglages et ses données.
|
|
1159
|
+
</>
|
|
1160
|
+
),
|
|
1161
|
+
},
|
|
1162
|
+
]}
|
|
1163
|
+
/>
|
|
1164
|
+
<H3>Que vérifier</H3>
|
|
1165
|
+
<ul>
|
|
1166
|
+
<li>Le rendu sur mobile et sur ordinateur, avec les thèmes officiels (SidiBou, Mahdia, Dogga) pour un module.</li>
|
|
1167
|
+
<li>
|
|
1168
|
+
L’aperçu de l’éditeur de pages : <C>isPreview</C> y vaut <C>true</C>, aucune action réelle ne doit partir (envoi de formulaire,
|
|
1169
|
+
appel payant).
|
|
1170
|
+
</li>
|
|
1171
|
+
<li>Les réglages : valeurs par défaut, champs vides, textes longs ; seuls les réglages <C>public</C> atteignent le navigateur.</li>
|
|
1172
|
+
<li>Les pages d’administration et les routes serveur avec un compte qui n’est pas super-administrateur.</li>
|
|
1173
|
+
<li>
|
|
1174
|
+
Les erreurs serveur : un module qui échoue au démarrage est signalé sur sa page dans l’administration, avec le message de l’erreur.
|
|
1175
|
+
</li>
|
|
1176
|
+
</ul>
|
|
1177
|
+
<Note tone="warn" title="Jamais en production">
|
|
1178
|
+
Le mode développeur accepte n’importe quel paquet non signé. Désactivez-le (supprimez la variable et redémarrez) dès la fin des tests.
|
|
1179
|
+
</Note>
|
|
1180
|
+
|
|
1181
|
+
<H2>3. Pendant la revue</H2>
|
|
1182
|
+
<p>
|
|
1183
|
+
Sans installation Snapify, l’analyse locale reste votre filet de sécurité : envoyez ensuite le paquet depuis votre{' '}
|
|
1184
|
+
<Link to="/docs/publier">espace vendeur</Link>. NOVALABS installe chaque version soumise sur un site de test, la relit et la teste
|
|
1185
|
+
avant de la signer. En cas de refus, la raison est indiquée : corrigez, augmentez la version si nécessaire, puis soumettez à nouveau.
|
|
1186
|
+
</p>
|
|
1187
|
+
<div className="flex flex-wrap gap-3 pt-2">
|
|
1188
|
+
<NextLink to="/docs/securite" label="Sécurité et revue" />
|
|
1189
|
+
<NextLink to="/docs/publier" label="Publier sur la marketplace" />
|
|
1190
|
+
</div>
|
|
1191
|
+
</>
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function Packages() {
|
|
1196
|
+
return (
|
|
1197
|
+
<>
|
|
1198
|
+
<p>
|
|
1199
|
+
L’outil <C>snapify</C> du <Link to="/docs/kit-developpeur">kit développeur</Link> compile les sources (React fourni par le site,
|
|
1200
|
+
Tailwind compilé et limité au module), calcule l’empreinte de chaque fichier et produit un paquet <C>.snapmod</C> ou{' '}
|
|
1201
|
+
<C>.snaptheme</C>, puis l’analyse comme la marketplace.
|
|
1202
|
+
</p>
|
|
1203
|
+
<Code lang="bash">{`
|
|
1204
|
+
# Depuis la racine du projet : paquet dans dist/, puis analyse
|
|
1205
|
+
npx snapify build
|
|
1206
|
+
|
|
1207
|
+
# Dossier de sortie personnalisé
|
|
1208
|
+
npx snapify build . --out paquets
|
|
1209
|
+
|
|
1210
|
+
# Analyser à nouveau un paquet, ou vérifier la signature d’un paquet reçu
|
|
1211
|
+
npx snapify check dist/monagence.mon-theme-1.0.0.snaptheme
|
|
1212
|
+
npx snapify verify monagence.mon-theme-1.0.0.snaptheme
|
|
1213
|
+
`}</Code>
|
|
1214
|
+
<H2>Format</H2>
|
|
1215
|
+
<p>Un paquet est un JSON compressé en gzip :</p>
|
|
1216
|
+
<Code lang="json">{`
|
|
1217
|
+
{
|
|
1218
|
+
"format": "snapmod/1",
|
|
1219
|
+
"manifest": {
|
|
1220
|
+
"…": "manifeste",
|
|
1221
|
+
"entries": { "public": "public.js", "server": "server.mjs", "admin": "admin.js" },
|
|
1222
|
+
"styles": "public.css",
|
|
1223
|
+
"files": { "public.js": { "sha256": "…", "size": 1234 } },
|
|
1224
|
+
"builtAt": "2026-10-15T08:00:00.000Z"
|
|
1225
|
+
},
|
|
1226
|
+
"files": { "public.js": "<base64>", "server.mjs": "<base64>" },
|
|
1227
|
+
"signature": { "alg": "ed25519", "kid": "novalabs-2026", "value": "<base64>" },
|
|
1228
|
+
"license": "SNAPIFY-LIC-…"
|
|
1229
|
+
}
|
|
1230
|
+
`}</Code>
|
|
1231
|
+
<ul>
|
|
1232
|
+
<li>
|
|
1233
|
+
Fichiers autorisés : <C>public.js</C>, <C>public.css</C>, <C>server.mjs</C>, <C>admin.js</C>, <C>admin.css</C>, <C>README.md</C>{' '}
|
|
1234
|
+
et la capture d’écran.
|
|
1235
|
+
</li>
|
|
1236
|
+
<li>Taille maximale : 20 Mo compressé, 40 Mo décompressé.</li>
|
|
1237
|
+
<li>
|
|
1238
|
+
La signature Ed25519 porte sur le manifeste, qui contient l’empreinte SHA-256 de chaque fichier : modifier un seul octet invalide le
|
|
1239
|
+
paquet.
|
|
1240
|
+
</li>
|
|
1241
|
+
</ul>
|
|
1242
|
+
<H2>Signature</H2>
|
|
1243
|
+
<p>
|
|
1244
|
+
Les sites en production n’installent que des paquets signés par une clé de confiance NOVALABS. C’est NOVALABS qui signe les versions
|
|
1245
|
+
validées lors de la revue : vous n’avez pas à gérer de clé.
|
|
1246
|
+
</p>
|
|
1247
|
+
<H2>Licences</H2>
|
|
1248
|
+
<p>
|
|
1249
|
+
Un module gratuit signé embarque une licence valable sur tous les domaines. Un module payant demande une clé de licence (
|
|
1250
|
+
<C>SNAPIFY-LIC-…</C>) liée au module, au client et à ses domaines (<C>exemple.tn</C>, <C>*.exemple.tn</C>), avec une date
|
|
1251
|
+
d’expiration facultative. Elle est vérifiée hors ligne à l’installation, au démarrage puis toutes les 10 minutes ; <C>localhost</C> est
|
|
1252
|
+
toujours accepté.
|
|
1253
|
+
</p>
|
|
1254
|
+
</>
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function Security() {
|
|
1259
|
+
return (
|
|
1260
|
+
<>
|
|
1261
|
+
<p>
|
|
1262
|
+
Les extensions s’exécutent chez des clients qui ne lisent pas leur code. Le modèle de sécurité de Snapify repose sur des contrôles
|
|
1263
|
+
automatiques et sur la revue de NOVALABS.
|
|
1264
|
+
</p>
|
|
1265
|
+
<Table
|
|
1266
|
+
head={['Contrôle', 'Moment']}
|
|
1267
|
+
rows={[
|
|
1268
|
+
['Signature du manifeste et empreinte de chaque fichier', 'Import, téléchargement, chaque démarrage, chaque activation'],
|
|
1269
|
+
[<>Compatibilité de l’API (<C>engines.snapify</C>)</>, 'Installation et démarrage'],
|
|
1270
|
+
['Licence : signature, module, domaine, expiration', 'Installation, saisie de clé, démarrage, toutes les 10 minutes'],
|
|
1271
|
+
['Permissions du contexte serveur', 'À chaque appel'],
|
|
1272
|
+
[<>Réglages validés ; seuls les réglages <C>public</C> atteignent le navigateur</>, 'Enregistrement et diffusion'],
|
|
1273
|
+
[<>Fichiers servis depuis la copie vérifiée ; <C>server.mjs</C> jamais exposé</>, 'Chaque requête'],
|
|
1274
|
+
]}
|
|
1275
|
+
/>
|
|
1276
|
+
<p>
|
|
1277
|
+
Une extension dont un contrôle échoue est <strong>bloquée</strong> (non chargée, routes désactivées) et signalée dans l’administration
|
|
1278
|
+
avec la raison. Une réinstallation depuis la marketplace la répare en conservant ses réglages.
|
|
1279
|
+
</p>
|
|
1280
|
+
<Note tone="warn" title="Ce n’est pas un bac à sable">
|
|
1281
|
+
Le code serveur s’exécute dans le processus Node du site. Les permissions encadrent l’API fournie, mais un code malveillant pourrait
|
|
1282
|
+
importer directement des modules Node : c’est pourquoi chaque version publiée est relue et signée par NOVALABS.
|
|
1283
|
+
</Note>
|
|
1284
|
+
<H2 id="analyse-automatique">Analyse automatique du paquet</H2>
|
|
1285
|
+
<p>
|
|
1286
|
+
À chaque import dans l’espace vendeur, le paquet est analysé <strong>sans exécuter son code</strong>. Le rapport classe les contrôles
|
|
1287
|
+
par rubrique ; une erreur bloque la création du produit ou de la version, un avertissement est signalé à la revue.
|
|
1288
|
+
</p>
|
|
1289
|
+
<Table
|
|
1290
|
+
head={['Rubrique', 'Refusé (erreur)', 'Signalé (avertissement)']}
|
|
1291
|
+
rows={[
|
|
1292
|
+
['Paquet', 'Format illisible, fichier altéré, signature NOVALABS qui ne correspond plus, licence invalide', 'Paquet non signé (NOVALABS signe la version validée)'],
|
|
1293
|
+
['Manifeste', <>Type incorrect, <C>engines.snapify</C> incompatible avec l’API {API_VERSION}</>, <><C>engines</C> à <C>*</C></>],
|
|
1294
|
+
['Code', <>Erreur de syntaxe, pas d’export par défaut, <C>import()</C> ou <C>require</C>, dépendance non incluse dans le bundle, module Node interdit (<C>fs</C>, <C>child_process</C>, <C>net</C>, <C>http</C>…)</>, 'Bundle très lourd'],
|
|
1295
|
+
['Permissions', <>Appel <C>ctx</C> sans la permission correspondante (<C>ctx.db</C> sans <C>database</C>, <C>ctx.env('X')</C> sans <C>env:X</C>…)</>, <>Permission déclarée mais inutilisée, hôte <C>net:</C> non déclaré</>],
|
|
1296
|
+
['Sécurité', <><C>process.env</C>, <C>fetch</C> direct, <C>eval</C>, accès aux internes du CMS, CSS hors du conteneur du module</>, <><C>eval</C> ou <C>document.cookie</C> côté navigateur, <C>@import</C> distant</>],
|
|
1297
|
+
['Présentation', 'Réglage public qui ressemble à un secret, capture dont le contenu ne correspond pas à l’extension', 'README, capture ou description absents ; capture < 800 × 450'],
|
|
1298
|
+
['Marketplace', <>Préfixe <C>novalabs.</C> ou <C>snapify.</C>, identifiant d’un autre éditeur, mise à jour avec un autre <C>id</C> ou une version non supérieure</>, '—'],
|
|
1299
|
+
]}
|
|
1300
|
+
/>
|
|
1301
|
+
<p>
|
|
1302
|
+
Le serveur utilise exactement la même analyse : on ne peut pas la contourner en appelant l’API directement.
|
|
1303
|
+
</p>
|
|
1304
|
+
<H2>Checklist avant de soumettre</H2>
|
|
1305
|
+
<ul>
|
|
1306
|
+
<li>Permissions minimales et justifiées ; aucun accès réseau vers un hôte non déclaré.</li>
|
|
1307
|
+
<li>Aucun secret dans les réglages publics ni dans le bundle navigateur.</li>
|
|
1308
|
+
<li>Entrées validées côté serveur (longueurs, formats) et erreurs renvoyées avec <C>ctx.error()</C>.</li>
|
|
1309
|
+
<li>Pas d’import de modules Node (<C>fs</C>, <C>child_process</C>…), pas de code obfusqué.</li>
|
|
1310
|
+
<li>Rendu testé sur mobile, dans l’aperçu de l’éditeur et avec les thèmes officiels.</li>
|
|
1311
|
+
</ul>
|
|
1312
|
+
</>
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function Publish() {
|
|
1317
|
+
const s = useMarketplaceSettings();
|
|
1318
|
+
const fee = s?.developerFeeLabel || '—';
|
|
1319
|
+
const commission = s ? `${Math.round(s.commissionBps / 100)} %` : '—';
|
|
1320
|
+
return (
|
|
1321
|
+
<>
|
|
1322
|
+
<p>
|
|
1323
|
+
La <a href={MARKETPLACE_URL}>marketplace Snapify</a> distribue vos modules et thèmes à tous les sites Snapify. Les clients les
|
|
1324
|
+
installent en un clic depuis leur administration.
|
|
1325
|
+
</p>
|
|
1326
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
1327
|
+
<div className="rounded-xl border border-slate-200 p-4">
|
|
1328
|
+
<div className="text-xs font-semibold uppercase tracking-wide text-slate-500">Inscription développeur</div>
|
|
1329
|
+
<div className="mt-1 text-2xl font-bold text-navy">{fee}</div>
|
|
1330
|
+
<div className="text-sm text-slate-500">paiement unique via Flouci</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
<div className="rounded-xl border border-slate-200 p-4">
|
|
1333
|
+
<div className="text-xs font-semibold uppercase tracking-wide text-slate-500">Commission marketplace</div>
|
|
1334
|
+
<div className="mt-1 text-2xl font-bold text-navy">{commission}</div>
|
|
1335
|
+
<div className="text-sm text-slate-500">sur chaque vente d’une extension payante</div>
|
|
1336
|
+
</div>
|
|
1337
|
+
</div>
|
|
1338
|
+
<H2>Étapes</H2>
|
|
1339
|
+
<Steps
|
|
1340
|
+
items={[
|
|
1341
|
+
{
|
|
1342
|
+
title: 'Créer un compte',
|
|
1343
|
+
body: (
|
|
1344
|
+
<>
|
|
1345
|
+
Sur <a href={`${MARKETPLACE_URL}/register`}>marketplace.snapify.tn</a>. Le même compte sert pour la Community.
|
|
1346
|
+
</>
|
|
1347
|
+
),
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
title: 'Devenir développeur',
|
|
1351
|
+
body: (
|
|
1352
|
+
<>
|
|
1353
|
+
Depuis <a href={`${MARKETPLACE_URL}/developer`}>la page Développeurs</a>, renseignez votre nom d’éditeur et réglez l’inscription.
|
|
1354
|
+
Votre espace vendeur s’ouvre aussitôt le paiement confirmé.
|
|
1355
|
+
</>
|
|
1356
|
+
),
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
title: 'Importer votre paquet',
|
|
1360
|
+
body: (
|
|
1361
|
+
<>
|
|
1362
|
+
Dans l’espace vendeur, déposez le <C>.snapmod</C> ou <C>.snaptheme</C>. Le paquet est analysé sans être exécuté (voir{' '}
|
|
1363
|
+
<a href="/docs/securite#analyse-automatique">l’analyse automatique</a>) : tant qu’une erreur subsiste, le produit ne peut pas être
|
|
1364
|
+
créé. Il est ensuite créé en brouillon à partir du manifeste.
|
|
1365
|
+
</>
|
|
1366
|
+
),
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
title: 'Compléter la fiche',
|
|
1370
|
+
body: 'Nom, descriptions courte et longue, catégorie, prix, icône et jusqu’à 8 captures d’écran.',
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
title: 'Soumettre pour review',
|
|
1374
|
+
body: 'NOVALABS relit le code et le teste. Le produit est publié, ou refusé avec une raison : corrigez puis soumettez à nouveau.',
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
title: 'Publier des mises à jour',
|
|
1378
|
+
body: (
|
|
1379
|
+
<>
|
|
1380
|
+
Importez une nouvelle version avec son changelog. Le même <C>id</C> et une <C>version</C> supérieure sont obligatoires ; les
|
|
1381
|
+
sites clients la proposent comme mise à jour.
|
|
1382
|
+
</>
|
|
1383
|
+
),
|
|
1384
|
+
},
|
|
1385
|
+
]}
|
|
1386
|
+
/>
|
|
1387
|
+
<H2>Revenus</H2>
|
|
1388
|
+
<p>
|
|
1389
|
+
L’espace vendeur affiche vos ventes, le montant brut, la commission et votre revenu net. Vous demandez vos virements depuis ce même
|
|
1390
|
+
espace.
|
|
1391
|
+
</p>
|
|
1392
|
+
<H2>Bonnes fiches produit</H2>
|
|
1393
|
+
<ul>
|
|
1394
|
+
<li>Une couverture 1280 × 720 lisible en petit : le bénéfice principal en une phrase.</li>
|
|
1395
|
+
<li>Des captures réelles : le rendu sur le site, puis les pages d’administration.</li>
|
|
1396
|
+
<li>Une description qui liste ce que fait le module, les permissions demandées et pourquoi.</li>
|
|
1397
|
+
<li>Un changelog clair pour chaque version.</li>
|
|
1398
|
+
</ul>
|
|
1399
|
+
<div className="flex flex-wrap gap-3 pt-2">
|
|
1400
|
+
<a
|
|
1401
|
+
href={`${MARKETPLACE_URL}/developer`}
|
|
1402
|
+
className="inline-flex items-center gap-2 rounded-lg bg-navy px-4 py-2.5 text-sm font-semibold text-white no-underline hover:bg-navy-2"
|
|
1403
|
+
>
|
|
1404
|
+
Devenir développeur
|
|
1405
|
+
</a>
|
|
1406
|
+
<a
|
|
1407
|
+
href={`${MARKETPLACE_URL}/developer/dashboard`}
|
|
1408
|
+
className="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-4 py-2.5 text-sm font-semibold text-navy no-underline hover:border-brand"
|
|
1409
|
+
>
|
|
1410
|
+
Ouvrir mon espace vendeur
|
|
1411
|
+
</a>
|
|
1412
|
+
</div>
|
|
1413
|
+
</>
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/* ------------------------------------------------------------------ Référence */
|
|
1418
|
+
|
|
1419
|
+
function Permissions() {
|
|
1420
|
+
return (
|
|
1421
|
+
<>
|
|
1422
|
+
<p>Demandez uniquement les permissions nécessaires : elles sont affichées au client avant l’installation.</p>
|
|
1423
|
+
<Table
|
|
1424
|
+
head={['Permission', 'Accorde']}
|
|
1425
|
+
rows={[
|
|
1426
|
+
[<C>site:read</C>, <>Informations publiques du site (<C>ctx.site()</C>).</>],
|
|
1427
|
+
[<C>storage</C>, <>Stockage clé/valeur propre au module (<C>ctx.storage</C>).</>],
|
|
1428
|
+
[<C>database</C>, <>Collections d’enregistrements (<C>ctx.db</C>).</>],
|
|
1429
|
+
[<C>files</C>, <>Images publiques sous <C>/uploads/modules/<id>/</C> (<C>ctx.files</C>, API 1.3).</>],
|
|
1430
|
+
[<C>http:routes</C>, <>Routes publiques sous <C>/api/modules/<id>/</C>.</>],
|
|
1431
|
+
[<C>http:cookies</C>, <>Lecture et dépôt de cookies <C>sm_*</C> (sessions de visiteurs).</>],
|
|
1432
|
+
[<C>admin:routes</C>, <>Routes réservées à l’administration.</>],
|
|
1433
|
+
[<C>mail:send</C>, 'Envoi d’e-mails via le SMTP du site.'],
|
|
1434
|
+
[<C>env:NOM</C>, <>Lecture d’une variable d’environnement précise, ex. <C>env:GEMINI_API_KEY</C>.</>],
|
|
1435
|
+
[<C>net:hôte</C>, <>Requêtes HTTPS vers cet hôte uniquement, ex. <C>net:api.openai.com</C>.</>],
|
|
1436
|
+
]}
|
|
1437
|
+
/>
|
|
1438
|
+
<H2>Version de l’API</H2>
|
|
1439
|
+
<p>
|
|
1440
|
+
L’API actuelle est la <strong>{API_VERSION}</strong>. La permission <C>files</C> et <C>@snapify/sdk/image</C> demandent{' '}
|
|
1441
|
+
<C>"engines": {'{ "snapify": "^1.3.0" }'}</C>.
|
|
1442
|
+
</p>
|
|
1443
|
+
</>
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function Cli() {
|
|
1448
|
+
return (
|
|
1449
|
+
<>
|
|
1450
|
+
<p>
|
|
1451
|
+
L’outil <C>snapify</C> est fourni par le paquet npm <C>snapify-sdk</C> (<Link to="/docs/kit-developpeur">kit développeur</Link>). Dans un projet, lancez-le avec{' '}
|
|
1452
|
+
<C>npx snapify</C> ou les scripts <C>npm run build</C> et <C>npm run typecheck</C>.
|
|
1453
|
+
</p>
|
|
1454
|
+
<Table
|
|
1455
|
+
head={['Commande', 'Rôle']}
|
|
1456
|
+
rows={[
|
|
1457
|
+
[
|
|
1458
|
+
<C>create [dossier]</C>,
|
|
1459
|
+
<>
|
|
1460
|
+
Crée un projet de module ; sans dossier, pose ses questions dans le terminal (c’est ce que lance <C>npm create snapify@latest</C>).
|
|
1461
|
+
Options : <C>--theme</C> (thème), <C>--id editeur.nom</C>, <C>--name</C>, <C>--author</C>, <C>--email</C>.
|
|
1462
|
+
</>,
|
|
1463
|
+
],
|
|
1464
|
+
[
|
|
1465
|
+
<C>build [dossier]</C>,
|
|
1466
|
+
<>
|
|
1467
|
+
Compile et empaquette, puis lance l’analyse marketplace. Options : <C>--out dossier</C> (<C>dist</C> par défaut),{' '}
|
|
1468
|
+
<C>--no-check</C>.
|
|
1469
|
+
</>,
|
|
1470
|
+
],
|
|
1471
|
+
[<C>check <paquet></C>, 'Analyse d’un paquet, identique à celle effectuée à l’envoi sur la marketplace.'],
|
|
1472
|
+
[<C>verify <paquet></C>, 'Contrôle format, manifeste, empreintes, signature NOVALABS et licence d’un paquet reçu.'],
|
|
1473
|
+
[<C>--version</C>, 'Version du kit.'],
|
|
1474
|
+
]}
|
|
1475
|
+
/>
|
|
1476
|
+
<p>
|
|
1477
|
+
Toutes les commandes renvoient un code de sortie non nul en cas d’erreur : <C>npm run build</C> peut servir tel quel dans une
|
|
1478
|
+
intégration continue.
|
|
1479
|
+
</p>
|
|
1480
|
+
<p>Exemple de sortie de <C>verify</C> pour un paquet téléchargé depuis la marketplace :</p>
|
|
1481
|
+
<Code lang="text">{`
|
|
1482
|
+
Module Akhbar Urgent 1.0.0 (hamdiayari.akhbar-urgent) — Hamdi Ayari
|
|
1483
|
+
Permissions : http:routes, admin:routes, database
|
|
1484
|
+
Fichiers : public.js, public.css, admin.js, admin.css, server.mjs, README.md, screenshot.jpg
|
|
1485
|
+
Signature : valide (NOVALABS, clé novalabs-local-2026)
|
|
1486
|
+
Licence : valide (free)
|
|
1487
|
+
`}</Code>
|
|
1488
|
+
<H2>Variables de l’outil</H2>
|
|
1489
|
+
<Table
|
|
1490
|
+
head={['Variable', 'Effet']}
|
|
1491
|
+
rows={[
|
|
1492
|
+
[<C>NO_COLOR=1</C>, 'Sortie sans couleurs (journaux d’intégration continue).'],
|
|
1493
|
+
[<C>SNAPIFY_DEBUG=1</C>, 'Affiche la pile d’appels complète en cas d’erreur de compilation.'],
|
|
1494
|
+
]}
|
|
1495
|
+
/>
|
|
1496
|
+
<H2>Variables d’un site Snapify</H2>
|
|
1497
|
+
<p>
|
|
1498
|
+
À placer dans le fichier <C>.env</C> d’une installation Snapify, puis redémarrer le site (voir{' '}
|
|
1499
|
+
<Link to="/docs/tester">Tester une extension</Link>).
|
|
1500
|
+
</p>
|
|
1501
|
+
<Table
|
|
1502
|
+
head={['Variable', 'Effet']}
|
|
1503
|
+
rows={[
|
|
1504
|
+
[<C>SNAPIFY_MODULES_DEV_MODE=true</C>, 'Site de test : accepte les paquets non signés. Jamais en production.'],
|
|
1505
|
+
[<C>MARKETPLACE_URL</C>, 'Catalogue lu par l’administration du site (HTTPS obligatoire).'],
|
|
1506
|
+
]}
|
|
1507
|
+
/>
|
|
1508
|
+
<H2>Intégration continue</H2>
|
|
1509
|
+
<Code lang="yaml" title=".github/workflows/snapify.yml">{`
|
|
1510
|
+
name: snapify
|
|
1511
|
+
on: [push, pull_request]
|
|
1512
|
+
jobs:
|
|
1513
|
+
build:
|
|
1514
|
+
runs-on: ubuntu-latest
|
|
1515
|
+
steps:
|
|
1516
|
+
- uses: actions/checkout@v4
|
|
1517
|
+
- uses: actions/setup-node@v4
|
|
1518
|
+
with: { node-version: 22 }
|
|
1519
|
+
- run: npm ci
|
|
1520
|
+
- run: npm run typecheck
|
|
1521
|
+
# échoue si l’analyse marketplace trouve une erreur
|
|
1522
|
+
- run: npm run build
|
|
1523
|
+
env: { NO_COLOR: '1' }
|
|
1524
|
+
`}</Code>
|
|
1525
|
+
</>
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const EXAMPLES: { id: string; name: string; shows: string }[] = [
|
|
1530
|
+
{ id: 'novalabs.whatsapp', name: 'WhatsApp', shows: 'Module 100 % navigateur : emplacement floating, réglages publics, aucune permission.' },
|
|
1531
|
+
{ id: 'novalabs.ai-chat', name: 'Chat IA', shows: 'Route serveur, clé d’API lue avec env:, appels sortants avec net:.' },
|
|
1532
|
+
{ id: 'novalabs.calendar', name: 'Calendrier', shows: 'Pages (routes), deux widgets, collections, page d’administration.' },
|
|
1533
|
+
{ id: 'novalabs.ai-articles', name: 'Articles IA', shows: 'Module payant : administration riche, files, plusieurs hôtes net:.' },
|
|
1534
|
+
{ id: 'novalabs.shop', name: 'Boutique', shows: 'Catalogue produits, widgets, services publiés avec ctx.provide().' },
|
|
1535
|
+
{ id: 'novalabs.cart', name: 'Panier', shows: 'requires, emplacement header-actions, page /panier/.' },
|
|
1536
|
+
{ id: 'novalabs.checkout', name: 'Commande', shows: 'Cookies, e-mails, ctx.lock(), dépendance facultative avec uses.' },
|
|
1537
|
+
{ id: 'novalabs.customers', name: 'Espace client', shows: 'Sessions de visiteurs, photos de profil (files), e-mails.' },
|
|
1538
|
+
{ id: 'novalabs.sidibou', name: 'SidiBou (thème)', shows: 'Thème complet, options de personnalisation, rendu de tous les blocs.' },
|
|
1539
|
+
{ id: 'novalabs.mahdia', name: 'Mahdia (thème)', shows: 'Thème boutique : intégration des modules e-commerce.' },
|
|
1540
|
+
];
|
|
1541
|
+
|
|
1542
|
+
function Examples() {
|
|
1543
|
+
return (
|
|
1544
|
+
<>
|
|
1545
|
+
<p>Les extensions officielles NOVALABS couvrent toutes les possibilités du SDK. Chacune illustre un cas d’usage :</p>
|
|
1546
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
1547
|
+
{EXAMPLES.map((e) => (
|
|
1548
|
+
<a
|
|
1549
|
+
key={e.id}
|
|
1550
|
+
href={product(e.id)}
|
|
1551
|
+
className="block rounded-xl border border-slate-200 p-4 no-underline transition hover:border-brand hover:shadow-sm"
|
|
1552
|
+
>
|
|
1553
|
+
<div className="font-semibold text-navy">{e.name}</div>
|
|
1554
|
+
<div className="mt-0.5 font-mono text-xs text-slate-400">{e.id}</div>
|
|
1555
|
+
<div className="mt-2 text-sm font-normal leading-relaxed text-slate-600">{e.shows}</div>
|
|
1556
|
+
</a>
|
|
1557
|
+
))}
|
|
1558
|
+
</div>
|
|
1559
|
+
</>
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/* ------------------------------------------------------------------ Index */
|
|
1564
|
+
|
|
1565
|
+
export const SECTIONS = ['Démarrer', 'Modules', 'Thèmes', 'Distribution', 'Référence'] as const;
|
|
1566
|
+
|
|
1567
|
+
export const PAGES: DocPage[] = [
|
|
1568
|
+
{ slug: 'introduction', section: 'Démarrer', title: 'Introduction', description: 'Modules, thèmes, SDK : ce que vous pouvez construire avec Snapify.', keywords: 'sdk concepts module theme kit', Body: Introduction },
|
|
1569
|
+
{ slug: 'kit-developpeur', section: 'Démarrer', title: 'Kit développeur', description: 'Installer le SDK depuis npm, créer un projet avec npm create snapify, téléchargement direct.', keywords: 'sdk installer telecharger npm npx create starter cli download snapify', Body: KitDownload },
|
|
1570
|
+
{ slug: 'demarrage-rapide', section: 'Démarrer', title: 'Premier module', description: 'Un module complet en cinq fichiers : manifeste, interface, serveur, construction.', keywords: 'quickstart tutoriel', Body: Quickstart },
|
|
1571
|
+
{ slug: 'modules/manifeste', section: 'Modules', title: 'Manifeste', description: 'Le fichier snapify.module.json : identité, permissions, emplacements, routes.', keywords: 'json id version engines icon screenshot widgets routes adminPages requires', Body: Manifest },
|
|
1572
|
+
{ slug: 'modules/reglages', section: 'Modules', title: 'Réglages', description: 'Champs générés dans l’administration, publics ou privés.', keywords: 'settings formulaire champs', Body: SettingsDoc },
|
|
1573
|
+
{ slug: 'modules/site-public', section: 'Modules', title: 'Site public', description: 'Emplacements, pages et widgets affichés aux visiteurs.', keywords: 'slots floating header-actions widget page react tailwind', Body: PublicSide },
|
|
1574
|
+
{ slug: 'modules/serveur', section: 'Modules', title: 'Serveur', description: 'Routes HTTP, cookies, e-mails et le contexte ctx.', keywords: 'api routes http ctx cookies mail fetch env', Body: ServerSide },
|
|
1575
|
+
{ slug: 'modules/base-de-donnees', section: 'Modules', title: 'Base de données', description: 'Collections d’enregistrements : insertion, filtres, recherche, tri.', keywords: 'database collection find insert', Body: Database },
|
|
1576
|
+
{ slug: 'modules/administration', section: 'Modules', title: 'Administration', description: 'Pages du back-office, composants et routes d’administration.', keywords: 'admin backoffice pages composants', Body: Admin },
|
|
1577
|
+
{ slug: 'modules/dependances', section: 'Modules', title: 'Dépendances', description: 'Faire coopérer plusieurs modules : requires, uses, services.', keywords: 'requires uses provide use services', Body: Dependencies },
|
|
1578
|
+
{ slug: 'themes/creer-un-theme', section: 'Thèmes', title: 'Créer un thème', description: 'Structure, contrat defineTheme, options et intégration des modules.', keywords: 'theme header footer layout usesnapify', Body: ThemeCreate },
|
|
1579
|
+
{ slug: 'themes/blocs', section: 'Thèmes', title: 'Rendu des blocs', description: 'Les 26 types de blocs de l’éditeur et leur typage.', keywords: 'blocks hero cta faq', Body: ThemeBlocks },
|
|
1580
|
+
{ slug: 'tester', section: 'Distribution', title: 'Tester une extension', description: 'Analyse locale, site Snapify de test en mode développeur, revue NOVALABS.', keywords: 'test tester mode developpeur dev mode importer reinstaller debug', Body: Testing },
|
|
1581
|
+
{ slug: 'paquets', section: 'Distribution', title: 'Paquets et licences', description: 'Construire et vérifier ; format .snapmod, signature et licences.', keywords: 'build snapmod snaptheme signature licence', Body: Packages },
|
|
1582
|
+
{ slug: 'securite', section: 'Distribution', title: 'Sécurité et revue', description: 'Contrôles automatiques et critères de la revue NOVALABS.', keywords: 'securite review', Body: Security },
|
|
1583
|
+
{ slug: 'publier', section: 'Distribution', title: 'Publier sur la marketplace', description: 'Inscription développeur, import du paquet, review, mises à jour, revenus.', keywords: 'marketplace vendre publier commission', Body: Publish },
|
|
1584
|
+
{ slug: 'reference/permissions', section: 'Référence', title: 'Permissions', description: 'Toutes les permissions et les versions de l’API.', keywords: 'permissions api version', Body: Permissions },
|
|
1585
|
+
{ slug: 'reference/cli', section: 'Référence', title: 'Outil en ligne de commande', description: 'Commandes create, build, check et verify.', keywords: 'cli npm npx commande snapify', Body: Cli },
|
|
1586
|
+
{ slug: 'reference/exemples', section: 'Référence', title: 'Exemples officiels', description: 'Les extensions NOVALABS et ce que chacune illustre.', keywords: 'exemples modules officiels', Body: Examples },
|
|
1587
|
+
];
|