Thèmes de site

Thèmes de site

Un thème est « juste un dossier » dans resources/scaffold/themes/ — l'ajouter au catalogue ne demande d'enregistrer son nom nulle part. Vérifié contre Niang\Core\Console\ProjectScaffolder et ThemePackageInstaller.

Catalogue

Huit thèmes livrés avec le framework, plus minimal (le squelette de démonstration, toujours en dernier et par défaut) :

SlugLibellé
vitrineSite vitrine / informationnel
ecommerceBoutique en ligne
blogBlog / magazine
portfolioPortfolio
landingLanding page one-page
authApplication avec comptes (inscription, connexion, espace membre) — voir Starter « auth »
apiAPI REST (JSON, jetons, CORS, OpenAPI) — voir Starter « api »
saasSaaS (organisations, membres, abonnements) — voir Starter « saas »
minimalMinimal — squelette de démonstration (défaut)

Voir Démarrage pour la question posée à l'installation, --type= et NIANG_SITE_TYPE.

Structure d'un thème

Un thème reproduit l'arborescence d'un projet — seuls les fichiers réellement fournis sont copiés :

text
resources/scaffold/themes/vitrine/
├── app/Controllers/
├── config/site.php
├── public/css/theme.css       # décline les variables de niang.css, jamais ne les réinvente
├── resources/views/
│   ├── layouts/
│   └── pages/
├── routes/web.php             # remplace entièrement celui du projet cible
├── tests/Feature/             # les tests du thème, livrés avec lui
└── theme.json                 # optionnel

theme.json (tous les champs facultatifs) :

CléRôle
labelLibellé affiché dans le catalogue (défaut : le slug capitalisé).
orderPosition dans le catalogue, croissante (défaut : 100).
modulesModules de resources/scaffold/modules/ à installer avec ce thème — voir ci-dessous.
removeChemins du projet cible à supprimer avant la copie (ex. la démo qu'un vrai thème remplace).
setupCommandes niang lancées automatiquement à la création du projet — voir ci-dessous.
next_stepsCommandes suggérées après installation ; celles déjà exécutées par setup avec succès en sont retirées.
notesInformations affichées après ces commandes (ex. identifiants d'un compte admin de test).

Comment shared/ et le thème se fusionnent

resources/scaffold/shared/ suit la même arborescence et fournit ce que tous les thèmes ont en commun (design system niang.css/niang.js, composants, layout de base, pages d'erreur) — jamais dupliqué dans chaque thème. ProjectScaffolder::install() fait, dans l'ordre :

  1. Lit le remove de shared/theme.json, puis celui de chaque module déclaré, puis celui du thème, et supprime ces chemins du projet cible (les démos que ce thème remplace — vues, tests spécifiques au squelette minimal).
  2. Copie shared/ par-dessus le projet cible.
  3. Copie chaque module déclaré dans modules (dans l'ordre où il est listé), par-dessus shared/.
  4. Copie le dossier du thème par-dessus, en dernier — ses fichiers gagnent en cas de collision, y compris sur ceux d'un module.

app/Middleware/, app/Models/ et database/migrations/ ne font partie ni de shared/ ni d'un thème : ils restent ceux du squelette de base copié par niang new, disponibles pour tous les thèmes qui en ont besoin (authentification, CSRF...).

L'installation d'un thème remplace, elle ne fusionne pas : c'est voulu, elle n'a lieu qu'une fois, à la création du projet, avant que quiconque n'ait touché à quoi que ce soit — fusionner serait plus fragile pour aucun bénéfice réel.

Modules : des briques partagées entre thèmes

Un module (resources/scaffold/modules/<nom>/, même arborescence qu'un thème) est une brique réutilisée par plusieurs thèmes sans être elle-même un type de site — l'espace d'administration, par exemple, commun à la boutique et au blog. Il se déclare dans theme.json et se copie après shared/ mais avant le thème (qui peut donc remplacer n'importe lequel de ses fichiers) :

resources/scaffold/themes/ecommerce/theme.json
{
    "label": "Boutique en ligne",
    "order": 20,
    "modules": ["admin"],
    "next_steps": ["./bin/niang migrate", "./bin/niang db:seed", "./bin/niang serve"],
    "notes": [
        "Compte administrateur de test (créé par db:seed) : admin@example.com / admin1234",
        "Connectez-vous sur /login : vous arrivez sur le tableau de bord /admin."
    ]
}

Le module admin (livré avec le framework) apporte les contrôleurs, vues et migrations d'un tableau de bord d'administration ; les thèmes ecommerce et blog le déclarent tous les deux plutôt que de dupliquer ce code. Un module déclaré qui n'existe pas sous resources/scaffold/modules/ fait échouer l'installation avec une erreur explicite plutôt qu'un projet à moitié construit.

Starter « auth » : une application avec comptes

Le point de départ d'un produit où les utilisateurs ont un compte. Sans contenu de démonstration à retirer : une page d'accueil, puis tout le parcours d'un membre.

bash
composer create-project niangpro/niangpro mon-app
# choix : auth (ou NIANG_SITE_TYPE=auth)
cd mon-app && ./bin/niang serve
PageRôle
/register, /login, /logoutinscription, connexion (« se souvenir de moi », limitation des tentatives), déconnexion
/forgot-password, /verify-email/{id}liens signés et à durée limitée, envoyés par email
/auth/google/redirect, /auth/github/redirectconnexion OAuth, active dès que le fournisseur est configuré dans .env
/tableau-de-bordl'espace membre, là où commence votre produit (resources/views/pages/dashboard.php)
/compteprofil (une nouvelle adresse redevient « non vérifiée »), mot de passe, double authentification, suppression du compte et de ses jetons d'API

Après inscription ou connexion, un membre arrive sur User::homePath(), soit /tableau-de-bord. Les pages de compte viennent du module account, que d'autres thèmes peuvent déclarer pour obtenir les mêmes écrans d'authentification. Les tests livrés (tests/Feature/AccountTest.php, AuthStarterTest.php) couvrent le parcours complet, CSRF compris.

Starter « api » : une API REST JSON

Pour un backend d'application mobile ou de SPA : aucune page HTML, tout répond en JSON.

RouteRôle
POST /api/v1/registercrée un compte, renvoie son premier jeton
POST /api/v1/tokensemail, mot de passe, device_name (et code si la double authentification est active) : un jeton
DELETE /api/v1/tokens/currentrévoque le jeton utilisé (déconnexion de l'appareil)
GET /api/v1/mele compte du jeton
/api/v1/notes, /api/v1/notes/{id}ressource d'exemple : CRUD paginé ; la note d'un autre compte répond 404
bash
curl -X POST http://127.0.0.1:8000/api/v1/register -H 'Content-Type: application/json' \
  -d '{"name":"Awa","email":"awa@example.com","password":"secret123","device_name":"curl"}'
# {"token":"ed9c...","user":{...}}
curl http://127.0.0.1:8000/api/v1/notes -H 'Authorization: Bearer ed9c...'

Validation par FormRequest (app/Requests/), réponses par JsonResource (app/Resources/), CORS avec une route OPTIONS par chemin, limitation de débit sur l'inscription et la connexion. La description OpenAPI est générée à la création du projet dans public/openapi.json ; relancez ./bin/niang openapi après chaque changement de route.

Starter « saas » : organisations, membres, abonnements

La base d'un logiciel en ligne vendu à des équipes. Chaque organisation est un locataire : ses pages vivent sous /o/<slug> et ses données (projets, membres, invitations) ne sont jamais visibles des autres — une autre organisation répond 404. Les pages de compte viennent du module account, comme pour le starter « auth ».

PageQuiRôle
/organisationstout membre connectéses organisations, création d'une nouvelle (il en devient propriétaire)
/o/<slug>, /o/<slug>/projetsmembrestableau de bord, ressource d'exemple ; suppression réservée aux administrateurs
/o/<slug>/membresmembres (gestion : administrateurs)rôles, invitations par email, retrait, quitter l'organisation
/o/<slug>/abonnementadministrateurs (changement : propriétaires)plans, souscription, résiliation
/invitations/{jeton}la personne invitée, connectée avec l'adresse invitéerejoindre l'organisation ; lien à usage unique, valable 7 jours

Trois rôles : propriétaire, administrateur, membre. Une route exige un rôle minimal avec EnsureTeamMember::class . ':admin'. Il reste toujours au moins un propriétaire : le dernier ne peut être ni rétrogradé, ni retiré, ni partir, ni supprimer son compte tant que l'organisation a d'autres membres.

Plans et paiement

config/billing.php
'provider' => env('BILLING_PROVIDER', \App\Billing\FakeBillingProvider::class),
'plans' => [
    'free' => ['name' => 'Gratuit', 'price' => 0, 'limits' => ['projects' => 3, 'members' => 2]],
    'pro' => ['name' => 'Pro', 'price' => 29, 'limits' => ['projects' => null, 'members' => 25]],
],

Les limites sont appliquées (création de projet, invitation et acceptation d'une invitation) avec Billing::allows('projects'). Le paiement passe par l'interface App\Billing\BillingProvider : checkout() (URL de la page de paiement du prestataire), cancel(), et webhook() pour les confirmations reçues sur POST /billing/webhook, qui appliquent le changement avec Billing::apply(). Le fournisseur FakeBillingProvider active un plan sans paiement ; il est refusé avec APP_ENV=production. Pour Stripe, Paddle, PayDunya, CinetPay..., écrivez votre classe et indiquez-la dans BILLING_PROVIDER.

Créer un nouveau thème

Ajouter un thème = ajouter un dossier sous resources/scaffold/themes/<slug>/ — il apparaît de lui-même dans le catalogue, rien à enregistrer ailleurs :

bash
mkdir -p resources/scaffold/themes/magazine/{app/Controllers,resources/views,routes}
# ... construire le thème ...
./bin/niang new test-magazine --type=magazine   # vérifier qu'il s'installe correctement

Le slug doit correspondre à ^[a-z0-9][a-z0-9_-]*$ et être différent de minimal (réservé, toujours le squelette tel quel).

Publier un thème comme paquet Composer

Pour distribuer un thème séparément du framework (jalon « extensibilité des thèmes en paquets Composer séparés ») : un paquet Composer ordinaire, avec une clé extra.niangpro-theme pointant vers le dossier du thème (déjà nommé comme le slug voulu — son basename devient le slug) :

composer.json du paquet de thème
{
    "name": "acme/niangpro-theme-magazine",
    "extra": {
        "niangpro-theme": "resources/magazine"
    }
}
bash
./bin/niang theme:add acme/niangpro-theme-magazine

La commande lance composer require --dev, puis ThemePackageInstaller copie vendor/acme/niangpro-theme-magazine/resources/magazine/ vers resources/scaffold/themes/magazine/ — après ça, le thème est un dossier local ordinaire, visible dans niang new --type=magazine exactement comme les cinq thèmes livrés d'origine. Aucune modification de ProjectScaffolder requise : « un thème est juste un dossier » reste vrai quelle que soit son origine.

⏱ 8.92 ms 🗄 0 requête(s) SQL 🧠 4.00 MB ↩ 200