create-snapify-app 0.0.0-stage → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +35 -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 +123 -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,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapify
|
|
3
|
+
* © 2026 NOVALABS WEB DESIGN — Tunisie
|
|
4
|
+
* Contact : contact@novalabs.tn · +216 24 734 647
|
|
5
|
+
* Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
|
|
6
|
+
*/
|
|
7
|
+
import React, { useEffect, useState } from 'react';
|
|
8
|
+
import { ArrowRight, ArrowUpRight, CheckCircle2, ChevronRight, FileText, Phone, Plus, Quote } from 'lucide-react';
|
|
9
|
+
import type {
|
|
10
|
+
BlockStyle,
|
|
11
|
+
CardsData,
|
|
12
|
+
ChecklistData,
|
|
13
|
+
FaqData,
|
|
14
|
+
GalleryData,
|
|
15
|
+
PanelsData,
|
|
16
|
+
ShowcaseData,
|
|
17
|
+
StatsData,
|
|
18
|
+
StepsData,
|
|
19
|
+
TestimonialsData,
|
|
20
|
+
} from '@snapify/sdk/content';
|
|
21
|
+
import { ButtonRow, CountUp, cx, Eyebrow, Icon, Reveal, Section, SectionHeading, useCms, useDark, useGo } from '../kit';
|
|
22
|
+
|
|
23
|
+
const COLS = { 2: 'md:grid-cols-2', 3: 'md:grid-cols-2 lg:grid-cols-3', 4: 'sm:grid-cols-2 lg:grid-cols-4' } as const;
|
|
24
|
+
|
|
25
|
+
/* ------------------------------------------------------------------ Cartes */
|
|
26
|
+
|
|
27
|
+
export function CardsBlock({ d, style }: { d: CardsData; style: BlockStyle }) {
|
|
28
|
+
return (
|
|
29
|
+
<Section style={style}>
|
|
30
|
+
<SectionHeading h={d} />
|
|
31
|
+
<CardsGrid d={d} />
|
|
32
|
+
</Section>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CardsGrid({ d }: { d: CardsData }) {
|
|
37
|
+
const go = useGo();
|
|
38
|
+
const dark = useDark();
|
|
39
|
+
const { t } = useCms();
|
|
40
|
+
return (
|
|
41
|
+
<div className={cx('grid grid-cols-1 gap-5', COLS[d.columns] ?? COLS[3])}>
|
|
42
|
+
{d.items.map((it, idx) => {
|
|
43
|
+
const clickable = !!it.link;
|
|
44
|
+
const onClick = clickable ? () => go(it.link) : undefined;
|
|
45
|
+
const Tag = clickable ? 'button' : 'div';
|
|
46
|
+
|
|
47
|
+
if (d.style === 'hoverDark') {
|
|
48
|
+
return (
|
|
49
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
50
|
+
<Tag
|
|
51
|
+
type={clickable ? 'button' : undefined}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
className="group relative h-full w-full overflow-hidden rounded-[1.5rem] border border-px-border bg-white p-7 text-left transition-all duration-500 hover:border-transparent hover:shadow-[0_30px_60px_-30px_rgba(11,21,40,0.35)] sm:p-8"
|
|
54
|
+
>
|
|
55
|
+
<div className="absolute inset-0 translate-y-full bg-px-navy transition-transform duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:translate-y-0" />
|
|
56
|
+
<div className="absolute inset-x-0 top-0 h-1 origin-left scale-x-0 bg-gradient-to-r from-px-primary to-px-accent transition-transform duration-500 group-hover:scale-x-100" />
|
|
57
|
+
<span className="absolute right-7 top-6 select-none text-6xl font-bold text-slate-100 transition-colors group-hover:text-white/5">{String(idx + 1).padStart(2, '0')}</span>
|
|
58
|
+
<div className="relative">
|
|
59
|
+
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-px-primary/10 text-px-primary-dark transition-colors group-hover:bg-px-primary group-hover:text-px-navy">
|
|
60
|
+
<Icon name={it.icon} />
|
|
61
|
+
</div>
|
|
62
|
+
<h3 className="mt-6 pr-12 text-lg font-semibold text-px-navy transition-colors group-hover:text-white sm:text-xl">{t(it.title)}</h3>
|
|
63
|
+
{it.text ? (
|
|
64
|
+
<p className="mt-3 text-sm leading-relaxed text-slate-600 transition-colors group-hover:text-white/75">
|
|
65
|
+
{d.textPrefix ? <span className="font-semibold text-px-primary-dark group-hover:text-px-primary-light">{t(d.textPrefix)} </span> : null}
|
|
66
|
+
{t(it.text)}
|
|
67
|
+
</p>
|
|
68
|
+
) : null}
|
|
69
|
+
{it.linkLabel ? (
|
|
70
|
+
<span className="mt-6 inline-flex items-center gap-2 text-sm font-semibold text-px-navy transition-colors group-hover:text-px-primary-light">
|
|
71
|
+
{t(it.linkLabel)}
|
|
72
|
+
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
73
|
+
</span>
|
|
74
|
+
) : null}
|
|
75
|
+
</div>
|
|
76
|
+
</Tag>
|
|
77
|
+
</Reveal>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (d.style === 'image') {
|
|
82
|
+
return (
|
|
83
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
84
|
+
<Tag type={clickable ? 'button' : undefined} onClick={onClick} className="card-snapify group flex h-full w-full flex-col overflow-hidden text-left">
|
|
85
|
+
<div className="relative aspect-16/10 overflow-hidden bg-px-navy">
|
|
86
|
+
{it.image ? <img src={it.image} alt={it.title} loading="lazy" className="h-full w-full object-cover opacity-90 transition-transform duration-500 group-hover:scale-105" /> : null}
|
|
87
|
+
<div className="absolute inset-0 bg-gradient-to-t from-px-navy/80 via-transparent to-transparent" />
|
|
88
|
+
<h3 className="absolute bottom-4 left-4 right-4 text-xl font-semibold text-white">{t(it.title)}</h3>
|
|
89
|
+
</div>
|
|
90
|
+
<div className="flex-1 space-y-4 p-6">
|
|
91
|
+
{it.text ? <p className="text-xs leading-relaxed text-slate-600 sm:text-sm">{t(it.text)}</p> : null}
|
|
92
|
+
{it.tags.length ? (
|
|
93
|
+
<div className="space-y-2 border-t border-slate-100 pt-2">
|
|
94
|
+
{d.tagsLabel ? <div className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">{t(d.tagsLabel)}</div> : null}
|
|
95
|
+
<div className="flex flex-wrap gap-1.5">
|
|
96
|
+
{it.tags.map((tag) => (
|
|
97
|
+
<span key={tag} className="rounded bg-slate-100 px-2 py-0.5 text-[10px] text-slate-700">
|
|
98
|
+
{t(tag)}
|
|
99
|
+
</span>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
) : null}
|
|
104
|
+
</div>
|
|
105
|
+
{it.linkLabel ? (
|
|
106
|
+
<div className="flex items-center justify-between border-t border-slate-100 px-6 pb-6 pt-4 text-xs font-semibold text-px-primary-dark">
|
|
107
|
+
<span>{t(it.linkLabel)}</span>
|
|
108
|
+
<ChevronRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
109
|
+
</div>
|
|
110
|
+
) : null}
|
|
111
|
+
</Tag>
|
|
112
|
+
</Reveal>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (d.style === 'numbered') {
|
|
117
|
+
return (
|
|
118
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
119
|
+
<div className="card-snapify h-full space-y-3 bg-white p-6">
|
|
120
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-px-primary/10 text-xs font-bold text-px-primary-dark">{String(idx + 1).padStart(2, '0')}</div>
|
|
121
|
+
{it.title ? <h3 className="text-base font-semibold text-px-navy">{t(it.title)}</h3> : null}
|
|
122
|
+
{it.text ? <p className="text-sm font-medium leading-relaxed text-slate-700">{t(it.text)}</p> : null}
|
|
123
|
+
</div>
|
|
124
|
+
</Reveal>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (d.style === 'minimal') {
|
|
129
|
+
return (
|
|
130
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
131
|
+
<Tag type={clickable ? 'button' : undefined} onClick={onClick} className={cx('group flex h-full w-full flex-col justify-between gap-4 text-left', clickable && 'card-snapify p-6')}>
|
|
132
|
+
<div>
|
|
133
|
+
{it.icon ? (
|
|
134
|
+
<div className={cx('mb-4 flex h-10 w-10 items-center justify-center rounded-xl', dark ? 'bg-white/10 text-px-primary-light' : 'bg-px-primary/10 text-px-primary-dark')}>
|
|
135
|
+
<Icon name={it.icon} />
|
|
136
|
+
</div>
|
|
137
|
+
) : null}
|
|
138
|
+
<h3 className={cx('text-base font-semibold transition-colors', dark ? 'text-white' : 'text-px-navy group-hover:text-px-primary-dark')}>{t(it.title)}</h3>
|
|
139
|
+
{it.text ? <p className={cx('mt-2 text-sm leading-relaxed', dark ? 'text-white/70' : 'text-slate-600')}>{t(it.text)}</p> : null}
|
|
140
|
+
</div>
|
|
141
|
+
{it.linkLabel ? (
|
|
142
|
+
<span className="flex items-center gap-1 text-xs font-semibold text-px-navy group-hover:text-px-primary-dark">
|
|
143
|
+
{t(it.linkLabel)} <ChevronRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
144
|
+
</span>
|
|
145
|
+
) : null}
|
|
146
|
+
</Tag>
|
|
147
|
+
</Reveal>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
153
|
+
<Tag type={clickable ? 'button' : undefined} onClick={onClick} className={cx('card-snapify group flex h-full w-full flex-col justify-between p-7 text-left', clickable && 'hover:border-px-primary/60')}>
|
|
154
|
+
<div className="space-y-4">
|
|
155
|
+
{it.icon ? (
|
|
156
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-xl border border-slate-200 bg-px-surface text-px-navy transition-colors group-hover:bg-px-primary/10 group-hover:text-px-primary-dark">
|
|
157
|
+
<Icon name={it.icon} className="h-6 w-6" />
|
|
158
|
+
</div>
|
|
159
|
+
) : null}
|
|
160
|
+
<div>
|
|
161
|
+
<h3 className="text-lg font-semibold text-px-navy transition-colors group-hover:text-px-primary-dark">{t(it.title)}</h3>
|
|
162
|
+
{it.text ? <p className="mt-2 text-xs leading-relaxed text-slate-500 sm:text-sm">{t(it.text)}</p> : null}
|
|
163
|
+
</div>
|
|
164
|
+
{it.tags.length ? (
|
|
165
|
+
<div className="pt-2">
|
|
166
|
+
{d.tagsLabel ? <div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-slate-400">{t(d.tagsLabel)}</div> : null}
|
|
167
|
+
<div className="flex flex-wrap gap-1.5">
|
|
168
|
+
{it.tags.map((tag) => (
|
|
169
|
+
<span key={tag} className="rounded bg-slate-100 px-2 py-0.5 font-mono text-[10px] text-slate-700">
|
|
170
|
+
{t(tag)}
|
|
171
|
+
</span>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
) : null}
|
|
176
|
+
</div>
|
|
177
|
+
{it.linkLabel ? (
|
|
178
|
+
<div className="mt-4 flex items-center justify-between border-t border-slate-100 pt-6 text-xs font-semibold text-px-primary-dark group-hover:text-px-navy">
|
|
179
|
+
<span>{t(it.linkLabel)}</span>
|
|
180
|
+
<ChevronRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
181
|
+
</div>
|
|
182
|
+
) : null}
|
|
183
|
+
</Tag>
|
|
184
|
+
</Reveal>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ------------------------------------------------------------------ Vitrine à onglets */
|
|
192
|
+
|
|
193
|
+
export function ShowcaseBlock({ d, style }: { d: ShowcaseData; style: BlockStyle }) {
|
|
194
|
+
const [active, setActive] = useState(0);
|
|
195
|
+
const [paused, setPaused] = useState(false);
|
|
196
|
+
const go = useGo();
|
|
197
|
+
const { t } = useCms();
|
|
198
|
+
const n = d.items.length;
|
|
199
|
+
const interval = Math.max(d.interval || 6, 2) * 1000;
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (paused || n < 2) return;
|
|
202
|
+
const timer = setTimeout(() => setActive((i) => (i + 1) % n), interval);
|
|
203
|
+
return () => clearTimeout(timer);
|
|
204
|
+
}, [active, paused, n, interval]);
|
|
205
|
+
if (!n) return null;
|
|
206
|
+
const item = d.items[Math.min(active, n - 1)];
|
|
207
|
+
return (
|
|
208
|
+
<Section style={style}>
|
|
209
|
+
<SectionHeading h={d} />
|
|
210
|
+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
|
211
|
+
<Reveal className="lg:col-span-5">
|
|
212
|
+
<div className="flex flex-col gap-2">
|
|
213
|
+
{d.items.map((it, idx) => {
|
|
214
|
+
const on = idx === active;
|
|
215
|
+
return (
|
|
216
|
+
<button
|
|
217
|
+
key={it.id}
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={() => setActive(idx)}
|
|
220
|
+
className={cx(
|
|
221
|
+
'relative flex w-full items-center gap-4 overflow-hidden rounded-2xl px-5 py-4 text-left transition-all duration-300',
|
|
222
|
+
on ? 'border border-px-border bg-white shadow-[0_20px_40px_-25px_rgba(11,21,40,0.35)]' : 'border border-transparent hover:bg-white/70'
|
|
223
|
+
)}
|
|
224
|
+
>
|
|
225
|
+
<span className={cx('flex h-10 w-10 shrink-0 items-center justify-center rounded-xl transition-colors', on ? 'bg-gradient-to-br from-px-primary to-px-accent text-white' : 'border border-px-border bg-white text-slate-500')}>
|
|
226
|
+
<Icon name={it.icon} />
|
|
227
|
+
</span>
|
|
228
|
+
<span className={cx('flex-1 text-sm font-semibold sm:text-base', on ? 'text-px-navy' : 'text-slate-500')}>{t(it.title)}</span>
|
|
229
|
+
<ArrowRight className={cx('h-4 w-4 transition-all', on ? 'translate-x-0 text-px-primary-dark opacity-100' : '-translate-x-2 opacity-0')} />
|
|
230
|
+
{on ? (
|
|
231
|
+
<span
|
|
232
|
+
key={`${active}-${paused}`}
|
|
233
|
+
className="px-progress absolute bottom-0 left-0 h-[3px] w-full bg-gradient-to-r from-px-primary to-px-accent"
|
|
234
|
+
style={{ animationDuration: `${interval}ms`, animationPlayState: paused ? 'paused' : 'running' }}
|
|
235
|
+
/>
|
|
236
|
+
) : null}
|
|
237
|
+
</button>
|
|
238
|
+
);
|
|
239
|
+
})}
|
|
240
|
+
</div>
|
|
241
|
+
</Reveal>
|
|
242
|
+
<Reveal delay={120} className="lg:col-span-7">
|
|
243
|
+
<div className="relative h-full min-h-[440px] overflow-hidden rounded-[1.75rem] bg-px-navy">
|
|
244
|
+
{d.items.map((it, idx) =>
|
|
245
|
+
it.image ? (
|
|
246
|
+
<img
|
|
247
|
+
key={it.id}
|
|
248
|
+
src={it.image}
|
|
249
|
+
alt={it.title}
|
|
250
|
+
className={cx('absolute inset-0 h-full w-full object-cover transition-all duration-1000', idx === active ? 'scale-100 opacity-60' : 'scale-110 opacity-0')}
|
|
251
|
+
/>
|
|
252
|
+
) : null
|
|
253
|
+
)}
|
|
254
|
+
<div className="absolute inset-0 bg-gradient-to-t from-px-navy via-px-navy/60 to-px-navy/10" />
|
|
255
|
+
<div key={item.id} className="px-word-in absolute inset-x-0 bottom-0 p-7 text-white sm:p-10">
|
|
256
|
+
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/10 px-3 py-1 text-[11px] font-semibold text-px-primary-light backdrop-blur">
|
|
257
|
+
<Icon name={item.icon} className="h-4 w-4" />
|
|
258
|
+
{String(active + 1).padStart(2, '0')}
|
|
259
|
+
</div>
|
|
260
|
+
<h3 className="text-2xl font-semibold leading-tight sm:text-3xl">{t(item.title)}</h3>
|
|
261
|
+
{item.text ? <p className="mt-3 max-w-xl leading-relaxed text-white/75">{t(item.text)}</p> : null}
|
|
262
|
+
{item.chips.length ? (
|
|
263
|
+
<div className="mt-5 flex flex-wrap gap-2">
|
|
264
|
+
{item.chips.map((c) => (
|
|
265
|
+
<span key={c} className="rounded-full border border-white/10 bg-white/10 px-3 py-1.5 text-xs text-white/85">
|
|
266
|
+
{t(c)}
|
|
267
|
+
</span>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
270
|
+
) : null}
|
|
271
|
+
{item.buttonLabel && item.link ? (
|
|
272
|
+
<button type="button" onClick={() => go(item.link)} className="group mt-7 inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-px-navy transition-colors hover:bg-px-primary-light">
|
|
273
|
+
{t(item.buttonLabel)}
|
|
274
|
+
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
275
|
+
</button>
|
|
276
|
+
) : null}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</Reveal>
|
|
280
|
+
</div>
|
|
281
|
+
</Section>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ------------------------------------------------------------------ Étapes */
|
|
286
|
+
|
|
287
|
+
export function StepsBlock({ d, style }: { d: StepsData; style: BlockStyle }) {
|
|
288
|
+
const [active, setActive] = useState(0);
|
|
289
|
+
const { t } = useCms();
|
|
290
|
+
const n = d.items.length;
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (d.style !== 'timeline' || n < 2) return;
|
|
293
|
+
const timer = setInterval(() => setActive((i) => (i + 1) % n), 4500);
|
|
294
|
+
return () => clearInterval(timer);
|
|
295
|
+
}, [d.style, n]);
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<Section style={style}>
|
|
299
|
+
<SectionHeading h={d} />
|
|
300
|
+
{d.style === 'timeline' ? (
|
|
301
|
+
<div className="relative">
|
|
302
|
+
<div className="absolute left-[12.5%] right-[12.5%] top-7 hidden h-[3px] rounded-full bg-px-border lg:block">
|
|
303
|
+
<div className="h-full rounded-full bg-gradient-to-r from-px-primary to-px-accent transition-all duration-700 ease-out" style={{ width: `${n > 1 ? (active / (n - 1)) * 100 : 0}%` }} />
|
|
304
|
+
</div>
|
|
305
|
+
<div className={cx('grid grid-cols-1 gap-5 sm:grid-cols-2', n >= 4 ? 'lg:grid-cols-4' : 'lg:grid-cols-3')}>
|
|
306
|
+
{d.items.map((step, idx) => {
|
|
307
|
+
const on = idx === active;
|
|
308
|
+
const done = idx <= active;
|
|
309
|
+
return (
|
|
310
|
+
<Reveal key={step.id} delay={idx * 90}>
|
|
311
|
+
<button type="button" onClick={() => setActive(idx)} onMouseEnter={() => setActive(idx)} className="group flex h-full w-full flex-col text-left">
|
|
312
|
+
<div
|
|
313
|
+
className={cx(
|
|
314
|
+
'relative z-10 mx-auto flex h-14 w-14 items-center justify-center rounded-2xl text-sm font-bold transition-all duration-500 lg:mx-0 lg:ml-[calc(50%-28px)]',
|
|
315
|
+
on ? 'scale-110 bg-gradient-to-br from-px-primary to-px-accent text-white shadow-[0_12px_30px_-8px_rgba(0,112,243,0.6)]' : done ? 'bg-px-navy text-white' : 'border border-px-border bg-white text-slate-400'
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
{step.num}
|
|
319
|
+
</div>
|
|
320
|
+
<div className={cx('mt-6 flex-1 rounded-[1.25rem] border p-6 transition-all duration-500', on ? '-translate-y-1 border-px-primary/50 bg-white shadow-[0_24px_50px_-28px_rgba(11,21,40,0.4)]' : 'border-px-border bg-white/60')}>
|
|
321
|
+
<h3 className="text-base font-semibold leading-snug text-px-navy">{t(step.title)}</h3>
|
|
322
|
+
{step.text ? <p className={cx('mt-2 text-xs leading-relaxed text-slate-600 transition-opacity', on ? 'opacity-100' : 'opacity-70')}>{t(step.text)}</p> : null}
|
|
323
|
+
{step.deliverable ? (
|
|
324
|
+
<div className="mt-4 border-t border-dashed border-px-border pt-4">
|
|
325
|
+
<div className="text-[10px] font-semibold uppercase tracking-wider text-slate-400">{t(d.deliverableLabel)}</div>
|
|
326
|
+
<div className="mt-1 flex items-start gap-1.5 text-xs font-medium text-px-navy">
|
|
327
|
+
<CheckCircle2 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-px-primary-dark" />
|
|
328
|
+
{t(step.deliverable)}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
) : null}
|
|
332
|
+
</div>
|
|
333
|
+
</button>
|
|
334
|
+
</Reveal>
|
|
335
|
+
);
|
|
336
|
+
})}
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
) : d.style === 'list' ? (
|
|
340
|
+
<div className="space-y-6">
|
|
341
|
+
{d.items.map((step, idx) => (
|
|
342
|
+
<Reveal key={step.id} delay={idx * 80}>
|
|
343
|
+
<div className="card-snapify flex flex-col items-start gap-6 border border-px-border bg-white p-6 sm:p-8 md:flex-row">
|
|
344
|
+
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-px-primary to-px-accent font-mono text-lg font-bold text-white shadow-md">{step.num}</div>
|
|
345
|
+
<div className="flex-1 space-y-2">
|
|
346
|
+
<h3 className="text-lg font-semibold text-px-navy">{t(step.title)}</h3>
|
|
347
|
+
{step.text ? <p className="text-sm leading-relaxed text-slate-600">{t(step.text)}</p> : null}
|
|
348
|
+
{step.deliverable ? (
|
|
349
|
+
<div className="flex items-center gap-2 border-t border-slate-100 pt-3 text-xs font-semibold text-px-primary-dark">
|
|
350
|
+
<FileText className="h-4 w-4" />
|
|
351
|
+
<span>
|
|
352
|
+
{t(d.deliverableLabel)} : {t(step.deliverable)}
|
|
353
|
+
</span>
|
|
354
|
+
</div>
|
|
355
|
+
) : null}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</Reveal>
|
|
359
|
+
))}
|
|
360
|
+
</div>
|
|
361
|
+
) : (
|
|
362
|
+
<div className="card-snapify border border-px-border bg-white p-8 sm:p-10">
|
|
363
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
364
|
+
{d.items.map((step) => (
|
|
365
|
+
<div key={step.id} className="space-y-2 rounded-xl border border-slate-200/70 bg-px-surface p-4">
|
|
366
|
+
<div className="font-mono text-xs font-bold text-px-primary-dark">Étape {step.num}</div>
|
|
367
|
+
<div className="text-xs font-semibold text-px-navy">{t(step.title)}</div>
|
|
368
|
+
{step.deliverable ? (
|
|
369
|
+
<div className="text-[11px] text-slate-500">
|
|
370
|
+
{t(d.deliverableLabel)} : {t(step.deliverable)}
|
|
371
|
+
</div>
|
|
372
|
+
) : null}
|
|
373
|
+
</div>
|
|
374
|
+
))}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
{d.buttons.length ? (
|
|
379
|
+
<Reveal className="mt-12">
|
|
380
|
+
<ButtonRow buttons={d.buttons} center />
|
|
381
|
+
</Reveal>
|
|
382
|
+
) : null}
|
|
383
|
+
</Section>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ------------------------------------------------------------------ Chiffres */
|
|
388
|
+
|
|
389
|
+
export function StatsBlock({ d, style }: { d: StatsData; style: BlockStyle }) {
|
|
390
|
+
const dark = useDark();
|
|
391
|
+
const { t } = useCms();
|
|
392
|
+
const value = (it: StatsData['items'][number]) => {
|
|
393
|
+
const num = Number(String(it.value).replace(/\s/g, '').replace(',', '.'));
|
|
394
|
+
return d.countUp && Number.isFinite(num) && String(it.value).trim() !== '' ? (
|
|
395
|
+
<CountUp end={num} prefix={it.prefix} suffix={it.suffix} />
|
|
396
|
+
) : (
|
|
397
|
+
<>
|
|
398
|
+
{it.prefix}
|
|
399
|
+
{t(it.value)}
|
|
400
|
+
{it.suffix}
|
|
401
|
+
</>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
return (
|
|
405
|
+
<Section style={style}>
|
|
406
|
+
<SectionHeading h={d} />
|
|
407
|
+
{d.style === 'strip' ? (
|
|
408
|
+
<Reveal>
|
|
409
|
+
<div className={cx('grid grid-cols-2 gap-4 rounded-2xl border p-6 md:grid-cols-4', dark ? 'border-white/10 bg-white/5' : 'border-px-border bg-white shadow-xs')}>
|
|
410
|
+
{d.items.map((it) => (
|
|
411
|
+
<div key={it.id} className={cx('border-r pr-4 last:border-r-0', dark ? 'border-white/10' : 'border-slate-100')}>
|
|
412
|
+
<div className={cx('text-[11px] font-medium uppercase tracking-wider', dark ? 'text-white/50' : 'text-slate-400')}>{t(it.label)}</div>
|
|
413
|
+
<div className={cx('mt-0.5 text-base font-bold sm:text-lg', dark ? 'text-white' : 'text-px-navy')}>{value(it)}</div>
|
|
414
|
+
</div>
|
|
415
|
+
))}
|
|
416
|
+
</div>
|
|
417
|
+
</Reveal>
|
|
418
|
+
) : d.style === 'inline' ? (
|
|
419
|
+
<div className={cx('grid grid-cols-2 gap-8', d.items.length >= 4 ? 'lg:grid-cols-4' : 'lg:grid-cols-3')}>
|
|
420
|
+
{d.items.map((it, idx) => (
|
|
421
|
+
<Reveal key={it.id} delay={idx * 80} className="text-center">
|
|
422
|
+
<div className={cx('text-4xl font-bold tracking-tight sm:text-5xl', dark ? 'text-white' : 'bg-gradient-to-r from-px-primary-dark to-px-accent bg-clip-text text-transparent')}>{value(it)}</div>
|
|
423
|
+
<div className={cx('mt-2 text-sm font-semibold', dark ? 'text-white/80' : 'text-px-navy')}>{t(it.label)}</div>
|
|
424
|
+
{it.hint ? <div className={cx('mt-1 text-xs', dark ? 'text-white/55' : 'text-slate-500')}>{t(it.hint)}</div> : null}
|
|
425
|
+
</Reveal>
|
|
426
|
+
))}
|
|
427
|
+
</div>
|
|
428
|
+
) : (
|
|
429
|
+
<div className={cx('grid grid-cols-2 gap-4', d.items.length >= 4 ? 'lg:grid-cols-4' : 'lg:grid-cols-3')}>
|
|
430
|
+
{d.items.map((it, idx) => (
|
|
431
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
432
|
+
<div className="group h-full rounded-[1.5rem] border border-px-border bg-white p-6 transition-all duration-500 hover:-translate-y-1 hover:shadow-[0_24px_50px_-30px_rgba(0,112,243,0.4)] sm:p-7">
|
|
433
|
+
<div className="bg-gradient-to-r from-px-primary-dark to-px-accent bg-clip-text text-3xl font-bold leading-none tracking-tight text-transparent sm:text-[2.6rem]">{value(it)}</div>
|
|
434
|
+
<div className="mt-3 text-sm font-semibold text-px-navy">{t(it.label)}</div>
|
|
435
|
+
{it.hint ? <div className="mt-1 text-xs text-slate-500">{t(it.hint)}</div> : null}
|
|
436
|
+
<div className="mt-5 h-1 overflow-hidden rounded-full bg-px-border-soft">
|
|
437
|
+
<div className="h-full w-1/3 bg-gradient-to-r from-px-primary to-px-accent transition-all duration-700 group-hover:w-full" />
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</Reveal>
|
|
441
|
+
))}
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
444
|
+
</Section>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ------------------------------------------------------------------ Panneaux extensibles */
|
|
449
|
+
|
|
450
|
+
export function PanelsBlock({ d, style }: { d: PanelsData; style: BlockStyle }) {
|
|
451
|
+
const [active, setActive] = useState(Math.min(1, Math.max(d.items.length - 1, 0)));
|
|
452
|
+
const go = useGo();
|
|
453
|
+
const { t } = useCms();
|
|
454
|
+
return (
|
|
455
|
+
<Section style={style}>
|
|
456
|
+
<SectionHeading h={d} />
|
|
457
|
+
<Reveal>
|
|
458
|
+
<div className="flex flex-col gap-4 lg:h-[520px] lg:flex-row">
|
|
459
|
+
{d.items.map((it, idx) => {
|
|
460
|
+
const on = idx === active;
|
|
461
|
+
return (
|
|
462
|
+
<div
|
|
463
|
+
key={it.id}
|
|
464
|
+
role="button"
|
|
465
|
+
tabIndex={0}
|
|
466
|
+
onMouseEnter={() => setActive(idx)}
|
|
467
|
+
onFocus={() => setActive(idx)}
|
|
468
|
+
onClick={() => go(it.link)}
|
|
469
|
+
onKeyDown={(e) => e.key === 'Enter' && go(it.link)}
|
|
470
|
+
className={cx('group relative h-[340px] cursor-pointer overflow-hidden rounded-[1.75rem] transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] lg:h-full', on ? 'lg:flex-[2.4]' : 'lg:flex-1')}
|
|
471
|
+
>
|
|
472
|
+
{it.image ? <img src={it.image} alt={it.title} className="absolute inset-0 h-full w-full object-cover transition-transform duration-[1200ms] group-hover:scale-105" /> : null}
|
|
473
|
+
<div className={cx('absolute inset-0 transition-colors duration-700', on ? 'bg-gradient-to-t from-px-navy via-px-navy/50 to-transparent' : 'bg-px-navy/60')} />
|
|
474
|
+
<div className="absolute inset-x-0 bottom-0 p-6 text-white sm:p-8">
|
|
475
|
+
{it.icon ? (
|
|
476
|
+
<div className="mb-4 flex h-11 w-11 items-center justify-center rounded-xl border border-white/20 bg-white/15 backdrop-blur">
|
|
477
|
+
<Icon name={it.icon} />
|
|
478
|
+
</div>
|
|
479
|
+
) : null}
|
|
480
|
+
<h3 className="text-2xl font-semibold">{t(it.title)}</h3>
|
|
481
|
+
{it.text ? <p className="mt-1 text-sm text-white/75">{t(it.text)}</p> : null}
|
|
482
|
+
{it.bullets.length ? (
|
|
483
|
+
<div className={cx('grid transition-all duration-700', on ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0')}>
|
|
484
|
+
<ul className="space-y-2 overflow-hidden text-sm text-white/80">
|
|
485
|
+
{it.bullets.map((b) => (
|
|
486
|
+
<li key={b} className="flex items-start gap-2 first:pt-4">
|
|
487
|
+
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-px-primary-light" />
|
|
488
|
+
<span className="line-clamp-2">{t(b)}</span>
|
|
489
|
+
</li>
|
|
490
|
+
))}
|
|
491
|
+
</ul>
|
|
492
|
+
</div>
|
|
493
|
+
) : null}
|
|
494
|
+
{it.linkLabel ? (
|
|
495
|
+
<span className="mt-5 inline-flex items-center gap-2 text-sm font-semibold text-px-primary-light">
|
|
496
|
+
{t(it.linkLabel)} <ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
497
|
+
</span>
|
|
498
|
+
) : null}
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
})}
|
|
503
|
+
</div>
|
|
504
|
+
</Reveal>
|
|
505
|
+
</Section>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* ------------------------------------------------------------------ Galerie */
|
|
510
|
+
|
|
511
|
+
const ROTATIONS = ['-2deg', '1.5deg', '-1.5deg', '2deg', '-2.5deg', '1deg', '-1deg', '2.5deg'];
|
|
512
|
+
|
|
513
|
+
export function GalleryBlock({ d, style }: { d: GalleryData; style: BlockStyle }) {
|
|
514
|
+
const { t } = useCms();
|
|
515
|
+
const heading = (
|
|
516
|
+
<div className="mb-4 flex flex-col justify-between gap-6 md:flex-row md:items-end">
|
|
517
|
+
<SectionHeading h={d} className="mb-0" />
|
|
518
|
+
{d.handNote ? <div className="hidden shrink-0 -rotate-2 font-hand text-2xl text-px-accent md:block">{t(d.handNote)}</div> : null}
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
if (d.style === 'polaroid') {
|
|
522
|
+
return (
|
|
523
|
+
<Section style={{ ...style, width: 'full' }}>
|
|
524
|
+
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">{heading}</div>
|
|
525
|
+
<Reveal delay={120}>
|
|
526
|
+
<div className="relative">
|
|
527
|
+
<div className="pointer-events-none absolute inset-y-0 right-0 z-10 w-16 bg-gradient-to-l from-white to-transparent sm:w-28" />
|
|
528
|
+
<div className="flex snap-x snap-mandatory gap-6 overflow-x-auto px-4 pb-8 pt-6 [scrollbar-width:thin] sm:gap-8 sm:px-6 lg:px-[max(2rem,calc((100vw-80rem)/2+2rem))]">
|
|
529
|
+
{d.items.map((p, i) => (
|
|
530
|
+
<figure key={p.id} className="px-polaroid relative w-60 shrink-0 snap-center rounded-sm sm:w-72" style={{ transform: `rotate(${ROTATIONS[i % ROTATIONS.length]})` }}>
|
|
531
|
+
<span className="px-tape" aria-hidden="true" />
|
|
532
|
+
<div className="relative aspect-[4/3] overflow-hidden bg-slate-100">
|
|
533
|
+
{p.image ? <img src={p.image} alt={p.caption || ''} loading="lazy" className="absolute inset-0 h-full w-full object-cover" /> : null}
|
|
534
|
+
</div>
|
|
535
|
+
<figcaption className="py-3 text-center">
|
|
536
|
+
<span className="block font-hand text-xl leading-tight text-slate-700">{t(p.caption)}</span>
|
|
537
|
+
</figcaption>
|
|
538
|
+
</figure>
|
|
539
|
+
))}
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
</Reveal>
|
|
543
|
+
{d.note ? (
|
|
544
|
+
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
545
|
+
<p className="mt-2 text-[11px] text-slate-400">{t(d.note)}</p>
|
|
546
|
+
</div>
|
|
547
|
+
) : null}
|
|
548
|
+
</Section>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
return (
|
|
552
|
+
<Section style={style}>
|
|
553
|
+
{heading}
|
|
554
|
+
<div className={cx('mt-8', d.style === 'masonry' ? 'columns-1 gap-4 sm:columns-2 lg:columns-3 [&>*]:mb-4' : 'grid grid-cols-2 gap-4 lg:grid-cols-3')}>
|
|
555
|
+
{d.items.map((p, i) => (
|
|
556
|
+
<Reveal key={p.id} delay={(i % 6) * 60}>
|
|
557
|
+
<figure className="group relative overflow-hidden rounded-2xl">
|
|
558
|
+
{p.image ? <img src={p.image} alt={p.caption || ''} loading="lazy" className={cx('w-full object-cover transition-transform duration-700 group-hover:scale-105', d.style === 'grid' && 'aspect-[4/3]')} /> : null}
|
|
559
|
+
{p.caption ? (
|
|
560
|
+
<figcaption className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-px-navy/80 to-transparent p-4 pt-10 text-sm font-medium text-white opacity-0 transition-opacity group-hover:opacity-100">{t(p.caption)}</figcaption>
|
|
561
|
+
) : null}
|
|
562
|
+
</figure>
|
|
563
|
+
</Reveal>
|
|
564
|
+
))}
|
|
565
|
+
</div>
|
|
566
|
+
{d.note ? <p className="mt-4 text-[11px] text-slate-400">{t(d.note)}</p> : null}
|
|
567
|
+
</Section>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* ------------------------------------------------------------------ Garanties / listes */
|
|
572
|
+
|
|
573
|
+
export function ChecklistBlock({ d, style }: { d: ChecklistData; style: BlockStyle }) {
|
|
574
|
+
const { t } = useCms();
|
|
575
|
+
return (
|
|
576
|
+
<Section style={style}>
|
|
577
|
+
<SectionHeading h={d} />
|
|
578
|
+
<div className={cx('grid grid-cols-1 gap-5', d.cards.length >= 3 ? 'md:grid-cols-3' : 'md:grid-cols-2')}>
|
|
579
|
+
{d.cards.map((card, idx) => (
|
|
580
|
+
<Reveal key={card.id} delay={idx * 90}>
|
|
581
|
+
<div className="group relative h-full rounded-[1.5rem] bg-px-border p-[1px] transition-colors duration-500 hover:bg-gradient-to-br hover:from-px-primary hover:to-px-accent">
|
|
582
|
+
<div className="h-full rounded-[calc(1.5rem-1px)] bg-white p-7">
|
|
583
|
+
<div className="mb-6 flex items-center gap-3">
|
|
584
|
+
{card.icon ? (
|
|
585
|
+
<span className="flex h-10 w-10 items-center justify-center rounded-xl border border-px-border bg-px-surface text-px-primary-dark transition-all group-hover:border-transparent group-hover:bg-gradient-to-br group-hover:from-px-primary group-hover:to-px-accent group-hover:text-white">
|
|
586
|
+
<Icon name={card.icon} />
|
|
587
|
+
</span>
|
|
588
|
+
) : null}
|
|
589
|
+
<div>
|
|
590
|
+
{card.tag ? <span className="block text-xs font-semibold uppercase tracking-wider text-slate-500">{t(card.tag)}</span> : null}
|
|
591
|
+
{card.title ? <span className="block text-base font-semibold text-px-navy">{t(card.title)}</span> : null}
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
<ul className="space-y-4">
|
|
595
|
+
{card.entries.map((e) => (
|
|
596
|
+
<li key={e.id} className="flex items-start gap-3">
|
|
597
|
+
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-px-primary-dark" />
|
|
598
|
+
<div>
|
|
599
|
+
<div className="text-sm font-semibold text-px-navy">{t(e.title)}</div>
|
|
600
|
+
{e.text ? <div className="text-xs text-slate-500">{t(e.text)}</div> : null}
|
|
601
|
+
</div>
|
|
602
|
+
</li>
|
|
603
|
+
))}
|
|
604
|
+
</ul>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</Reveal>
|
|
608
|
+
))}
|
|
609
|
+
</div>
|
|
610
|
+
</Section>
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* ------------------------------------------------------------------ FAQ */
|
|
615
|
+
|
|
616
|
+
export function FaqBlock({ d, style }: { d: FaqData; style: BlockStyle }) {
|
|
617
|
+
const [open, setOpen] = useState<number | null>(0);
|
|
618
|
+
const { t, tokens } = useCms();
|
|
619
|
+
const dark = useDark();
|
|
620
|
+
const list = (
|
|
621
|
+
<div className="space-y-3">
|
|
622
|
+
{d.items.map((faq, index) => {
|
|
623
|
+
const isOpen = open === index;
|
|
624
|
+
return (
|
|
625
|
+
<Reveal key={faq.id} delay={index * 60}>
|
|
626
|
+
<div className={cx('rounded-[1.25rem] border bg-white transition-all duration-300', isOpen ? 'border-px-primary/50 shadow-[0_20px_40px_-30px_rgba(11,21,40,0.35)]' : 'border-px-border')}>
|
|
627
|
+
<button type="button" onClick={() => setOpen(isOpen ? null : index)} className="flex w-full items-center justify-between gap-4 p-5 text-left sm:p-6" aria-expanded={isOpen}>
|
|
628
|
+
<span className={cx('text-sm font-semibold transition-colors sm:text-base', isOpen ? 'text-px-primary-dark' : 'text-px-navy')}>{t(faq.question)}</span>
|
|
629
|
+
<span className={cx('flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-all duration-300', isOpen ? 'rotate-45 bg-px-navy text-white' : 'border border-px-border bg-px-surface text-slate-500')}>
|
|
630
|
+
<Plus className="h-4 w-4" />
|
|
631
|
+
</span>
|
|
632
|
+
</button>
|
|
633
|
+
<div className={cx('grid transition-all duration-500', isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]')}>
|
|
634
|
+
<div className="overflow-hidden">
|
|
635
|
+
<div className="cms-prose px-5 pb-6 text-sm leading-relaxed text-slate-600 sm:px-6" dangerouslySetInnerHTML={{ __html: t(faq.answer) }} />
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
</div>
|
|
639
|
+
</Reveal>
|
|
640
|
+
);
|
|
641
|
+
})}
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
if (d.layout === 'stacked') {
|
|
645
|
+
return (
|
|
646
|
+
<Section style={style}>
|
|
647
|
+
<SectionHeading h={{ ...d, align: 'center' }} />
|
|
648
|
+
<div className="mx-auto max-w-3xl">{list}</div>
|
|
649
|
+
</Section>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return (
|
|
653
|
+
<Section style={style}>
|
|
654
|
+
<div className="pointer-events-none absolute -left-16 bottom-0 select-none bg-gradient-to-b from-px-primary/12 to-px-accent/0 bg-clip-text text-[22rem] font-bold leading-[0.8] text-transparent sm:text-[28rem]" aria-hidden="true">
|
|
655
|
+
?
|
|
656
|
+
</div>
|
|
657
|
+
<div className="grid grid-cols-1 gap-10 lg:grid-cols-12">
|
|
658
|
+
<Reveal className="space-y-4 self-start lg:sticky lg:top-28 lg:col-span-4">
|
|
659
|
+
{d.eyebrow ? <Eyebrow>{t(d.eyebrow)}</Eyebrow> : null}
|
|
660
|
+
{d.title ? <h2 className={cx('text-3xl font-semibold leading-tight tracking-tight sm:text-[2.4rem]', dark ? 'text-white' : 'text-px-navy')}>{t(d.title)}</h2> : null}
|
|
661
|
+
{d.intro ? <p className={cx('text-sm', dark ? 'text-white/70' : 'text-slate-600')}>{t(d.intro)}</p> : null}
|
|
662
|
+
{d.showContactCard ? (
|
|
663
|
+
<a href={`tel:${tokens.phoneClean}`} className="mt-2 inline-flex items-center gap-3 rounded-2xl border border-px-border bg-white p-3 pr-5 transition-colors hover:border-px-primary">
|
|
664
|
+
<span className="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-px-primary to-px-accent text-white">
|
|
665
|
+
<Phone className="h-4 w-4" />
|
|
666
|
+
</span>
|
|
667
|
+
<span>
|
|
668
|
+
<span className="block text-[11px] text-slate-500">{t(d.contactLabel) || 'Une autre question ?'}</span>
|
|
669
|
+
<span className="block text-sm font-semibold text-px-navy">{tokens.phone}</span>
|
|
670
|
+
</span>
|
|
671
|
+
</a>
|
|
672
|
+
) : null}
|
|
673
|
+
</Reveal>
|
|
674
|
+
<div className="lg:col-span-8">{list}</div>
|
|
675
|
+
</div>
|
|
676
|
+
</Section>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ------------------------------------------------------------------ Témoignages */
|
|
681
|
+
|
|
682
|
+
export function TestimonialsBlock({ d, style }: { d: TestimonialsData; style: BlockStyle }) {
|
|
683
|
+
const { t } = useCms();
|
|
684
|
+
const dark = useDark();
|
|
685
|
+
return (
|
|
686
|
+
<Section style={style}>
|
|
687
|
+
<SectionHeading h={d} />
|
|
688
|
+
<div className={cx('grid grid-cols-1 gap-5', d.items.length >= 3 ? 'md:grid-cols-3' : 'md:grid-cols-2')}>
|
|
689
|
+
{d.items.map((it, idx) => (
|
|
690
|
+
<Reveal key={it.id} delay={idx * 80}>
|
|
691
|
+
<figure className={cx('flex h-full flex-col justify-between rounded-[1.5rem] border p-7', dark ? 'border-white/10 bg-white/5' : 'border-px-border bg-white')}>
|
|
692
|
+
<div>
|
|
693
|
+
<Quote className="h-7 w-7 text-px-primary" />
|
|
694
|
+
<blockquote className={cx('mt-4 text-[15px] leading-relaxed', dark ? 'text-white/85' : 'text-slate-700')}>{t(it.quote)}</blockquote>
|
|
695
|
+
</div>
|
|
696
|
+
<figcaption className="mt-6 flex items-center gap-3">
|
|
697
|
+
{it.image ? <img src={it.image} alt={it.author} className="h-11 w-11 rounded-full object-cover" /> : null}
|
|
698
|
+
<div>
|
|
699
|
+
<div className={cx('text-sm font-semibold', dark ? 'text-white' : 'text-px-navy')}>{t(it.author)}</div>
|
|
700
|
+
{it.role ? <div className={cx('text-xs', dark ? 'text-white/55' : 'text-slate-500')}>{t(it.role)}</div> : null}
|
|
701
|
+
</div>
|
|
702
|
+
</figcaption>
|
|
703
|
+
</figure>
|
|
704
|
+
</Reveal>
|
|
705
|
+
))}
|
|
706
|
+
</div>
|
|
707
|
+
</Section>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export { ArrowUpRight };
|