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) :
| Slug | Libellé |
|---|---|
vitrine | Site vitrine / informationnel |
ecommerce | Boutique en ligne |
blog | Blog / magazine |
portfolio | Portfolio |
landing | Landing page one-page |
auth | Application avec comptes (inscription, connexion, espace membre) — voir Starter « auth » |
api | API REST (JSON, jetons, CORS, OpenAPI) — voir Starter « api » |
saas | SaaS (organisations, membres, abonnements) — voir Starter « saas » |
minimal | Minimal — 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 :
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 |
|---|---|
label | Libellé affiché dans le catalogue (défaut : le slug capitalisé). |
order | Position dans le catalogue, croissante (défaut : 100). |
modules | Modules de resources/scaffold/modules/ à installer avec ce thème — voir ci-dessous. |
remove | Chemins du projet cible à supprimer avant la copie (ex. la démo qu'un vrai thème remplace). |
setup | Commandes niang lancées automatiquement à la création du projet — voir ci-dessous. |
next_steps | Commandes suggérées après installation ; celles déjà exécutées par setup avec succès en sont retirées. |
notes | Informations 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 :
- Lit le
removedeshared/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). - Copie
shared/par-dessus le projet cible. - Copie chaque module déclaré dans
modules(dans l'ordre où il est listé), par-dessusshared/. - 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) :
{
"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.
composer create-project niangpro/niangpro mon-app
# choix : auth (ou NIANG_SITE_TYPE=auth)
cd mon-app && ./bin/niang serve
| Page | Rôle |
|---|---|
/register, /login, /logout | inscription, 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/redirect | connexion OAuth, active dès que le fournisseur est configuré dans .env |
/tableau-de-bord | l'espace membre, là où commence votre produit (resources/views/pages/dashboard.php) |
/compte | profil (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.
| Route | Rôle |
|---|---|
POST /api/v1/register | crée un compte, renvoie son premier jeton |
POST /api/v1/tokens | email, mot de passe, device_name (et code si la double authentification est active) : un jeton |
DELETE /api/v1/tokens/current | révoque le jeton utilisé (déconnexion de l'appareil) |
GET /api/v1/me | le compte du jeton |
/api/v1/notes, /api/v1/notes/{id} | ressource d'exemple : CRUD paginé ; la note d'un autre compte répond 404 |
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 ».
| Page | Qui | Rôle |
|---|---|---|
/organisations | tout membre connecté | ses organisations, création d'une nouvelle (il en devient propriétaire) |
/o/<slug>, /o/<slug>/projets | membres | tableau de bord, ressource d'exemple ; suppression réservée aux administrateurs |
/o/<slug>/membres | membres (gestion : administrateurs) | rôles, invitations par email, retrait, quitter l'organisation |
/o/<slug>/abonnement | administrateurs (changement : propriétaires) | plans, souscription, résiliation |
/invitations/{jeton} | la personne invitée, connectée avec l'adresse invitée | rejoindre 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
'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 :
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) :
{
"name": "acme/niangpro-theme-magazine",
"extra": {
"niangpro-theme": "resources/magazine"
}
}
./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.