nico-tools 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +9 -13
- package/bin/commands/create-project.js +106 -0
- package/bin/commands/translate.js +12 -0
- package/bin/config/templates.js +9 -0
- package/bin/utils/file-utils.js +84 -0
- package/bin/utils/project-utils.js +49 -0
- package/package.json +4 -2
- package/templates/react-native/expo-clean-architecture/README.md +154 -0
- package/templates/react-native/expo-clean-architecture/app.config.ts +61 -0
- package/templates/react-native/expo-clean-architecture/assets/config/.gitkeep +3 -0
- package/templates/react-native/expo-clean-architecture/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/adaptive-icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/favicon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/partial-react-logo.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo@2x.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo@3x.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/splash-icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/babel.config.js +11 -0
- package/templates/react-native/expo-clean-architecture/docs/00-introduction.md +3 -0
- package/templates/react-native/expo-clean-architecture/docs/01-architecture.md +107 -0
- package/templates/react-native/expo-clean-architecture/package.json +78 -0
- package/templates/react-native/expo-clean-architecture/scripts/clean-src.sh +48 -0
- package/templates/react-native/expo-clean-architecture/scripts/generate-feature.sh +40 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/_layout.tsx +42 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/favorites.tsx +72 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/home.tsx +122 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/_layout.tsx +5 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/index.tsx +29 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/profile.tsx +22 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/_layout.tsx +20 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/details.tsx +124 -0
- package/templates/react-native/expo-clean-architecture/src/app/(public)/_layout.tsx +18 -0
- package/templates/react-native/expo-clean-architecture/src/app/(public)/login.tsx +31 -0
- package/templates/react-native/expo-clean-architecture/src/app/_layout.tsx +33 -0
- package/templates/react-native/expo-clean-architecture/src/app/index.tsx +8 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/api-constants.ts +10 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/image-constants.ts +3 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/query-keys.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/storage-keys.ts +3 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme-state.ts +35 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme.ts +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/components/app-icon.tsx +16 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/components/app-separator.tsx +26 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-color-scheme.ts +52 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-fonts.ts +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-styles.ts +28 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-colors.ts +21 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-fonts.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-sizes.ts +14 -0
- package/templates/react-native/expo-clean-architecture/src/core/di/injection-container.ts +53 -0
- package/templates/react-native/expo-clean-architecture/src/core/errors/index.ts +1 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/@types.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/rest-client.ts +144 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/result.ts +37 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/usecase.ts +5 -0
- package/templates/react-native/expo-clean-architecture/src/core/hooks/use-network.ts +18 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/@types/i18next.d.ts +11 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/index.ts +19 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/translations/fr.json +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service-impl.ts +29 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service.ts +26 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service-impl.ts +15 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service.ts +13 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/network-service-impl.ts +40 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/network-service.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-state.ts +38 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-status-enum.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/presentation/hooks/use-session.ts +18 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource-impl.ts +25 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource.ts +5 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/repositories/favorites-repository-impl.ts +46 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/repositories/favorites-repository.ts +8 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/add-to-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/clear-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/get-favorites-usecase.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/remove-from-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/components/favorites-card.tsx +77 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-add-favorite.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-clear-favorites.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-favorites.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-remove-favorite.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource-impl.ts +50 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource.ts +8 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-details-model.ts +67 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-model.ts +30 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/tmdb-response-model.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/repositories/movies-repository-impl.ts +34 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-details-entity.ts +28 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-entity.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/repositories/movies-repository.ts +25 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-movie-details-usecase.ts +26 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-random-movies-usecase.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/search-movies-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/components/movie-tile.tsx +69 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-movie-details.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-random-movies.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-search-movies.ts +22 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/local-storage-service-impl.test.ts +108 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/monitoring-service.test.ts +74 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/network-service.test.ts +117 -0
- package/templates/react-native/expo-clean-architecture/tests/features/auth/presentation/hooks/use-session.test.ts +69 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/datasources/favorites-datasource.test.ts +69 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/repositories/favorites-repository-impl.test.ts +124 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/add-to-favorites-usecase.test.ts +54 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/clear-favorites-usecase.test.ts +44 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/get-favorites-usecase.test.ts +74 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/remove-from-favorites-usecase.test.ts +52 -0
- package/templates/react-native/expo-clean-architecture/tests/setup.ts +9 -0
- package/templates/react-native/expo-clean-architecture/tsconfig.json +20 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Sommaire
|
|
6
|
+
|
|
7
|
+
- [Introduction](#introduction)
|
|
8
|
+
- [Stack technique](#stack-technique)
|
|
9
|
+
- [Injection de dépendances](#injection-de-dependances)
|
|
10
|
+
- [Structure](#structure)
|
|
11
|
+
- [Ressources utiles](#ressources-utiles)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Introduction
|
|
16
|
+
|
|
17
|
+
Le projet est développé avec [React Native](https://reactnative.dev/) et [Expo](https://expo.dev/), et suit les principes de [Clean Architecture]("TODO-Add-Link") en adoptant une approche **feature-based**.
|
|
18
|
+
|
|
19
|
+
L'application utilise le file-based routing de [Expo Router](https://docs.expo.dev/router/introduction/) afin de gérer la navigation entre les différents écrans. De ce fait, les écrans sont définis directement dans le dossier `src/app`.
|
|
20
|
+
|
|
21
|
+
Chaque fonctionnalité est encapsulée dans un dossier dédié dans le dossier `src/features` afin d'améliorer la lisibilité et la maintenabilité du code.
|
|
22
|
+
|
|
23
|
+
Les élements communs (constantes, utilitaires etc.) sont regroupés dans le dossier `src/core`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Stack technique
|
|
28
|
+
|
|
29
|
+
### Langage
|
|
30
|
+
|
|
31
|
+
- TypeScript
|
|
32
|
+
|
|
33
|
+
### Librairies clés
|
|
34
|
+
|
|
35
|
+
- [React Native](https://reactnative.dev/)
|
|
36
|
+
- [Expo](https://expo.dev/)
|
|
37
|
+
- [Expo Router](https://docs.expo.dev/router/introduction/) : gestion de la navigation entre les écrans, **file-based routing**.
|
|
38
|
+
- [@tanstack/react-query](https://tanstack.com/query/latest) : gestion d'état global serveur et caching de requêtes asynchrones
|
|
39
|
+
- [zustand](https://zustand.docs.pmnd.rs/) : gestion d'état global applicatif (authentification, configuration, etc.)
|
|
40
|
+
- [Inversify](https://github.com/inversify/InversifyJS) : gestion de l'injection de dépendances
|
|
41
|
+
- [@react-native-firebase/crashlytics](https://rnfirebase.io/) : monitoring des erreurs et crashs
|
|
42
|
+
- [react-i18next](https://react.i18next.com/) : gestion de l'internationalisation
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Structure
|
|
47
|
+
|
|
48
|
+
Chaque feature est systématiquement divisée en 3 couches :
|
|
49
|
+
|
|
50
|
+
### La couche "data"
|
|
51
|
+
|
|
52
|
+
Le dossier `data` est responsable de la **récupération des données** à partir de sources externes (API REST, base de données, etc.) ou internes (stockage local, données statiques).
|
|
53
|
+
|
|
54
|
+
Il se découpe de la manière suivante :
|
|
55
|
+
|
|
56
|
+
- `datasources` :
|
|
57
|
+
- Contient les abstractions et implémentations des sources de données.
|
|
58
|
+
- `models` :
|
|
59
|
+
- Contient les interfaces des modèles de données telles que retournées par les _datasources_.
|
|
60
|
+
- Ces interfaces se contentent simplement de **typer** les données brutes, sans transformation supplémentaire.
|
|
61
|
+
- `repositories` :
|
|
62
|
+
- Contient les implémentations des _repositories_ définis dans la couche _domain_.
|
|
63
|
+
|
|
64
|
+
### La couche "domain"
|
|
65
|
+
|
|
66
|
+
Le dossier `domain` est responsable de la **logique métier** de l'application. Il regroupe l'ensemble des règles, entités et cas d'utilisation nécessaire au bon fonctionnement de la feature concernée.
|
|
67
|
+
|
|
68
|
+
Il se découpe de la manière suivante :
|
|
69
|
+
|
|
70
|
+
- `entities` :
|
|
71
|
+
- Contient les interfaces des données telles qu'elles sont utilisées dans l'application (à la différence des _models_ de la couche _data_, elles ne sont pas limitées aux données brutes).
|
|
72
|
+
- `usecases` :
|
|
73
|
+
- Contient les cas d'utilisation de la feature.
|
|
74
|
+
- Exemples : ajouter/supprimer un favori, récupérer une liste de films etc.
|
|
75
|
+
- `repositories` :
|
|
76
|
+
- Contient les interfaces des _repositories_ implémentés dans la couche _data_.
|
|
77
|
+
|
|
78
|
+
**Point importants** :
|
|
79
|
+
|
|
80
|
+
- La couche _domain_ ne doit dépendre d'aucune autre couche (ni _data_, ni _presentation_) et être indépendante toute librairie externe. Cela permet un meilleur testabilité et maintenabilité du code sur le long terme.
|
|
81
|
+
|
|
82
|
+
### La couche "presentation"
|
|
83
|
+
|
|
84
|
+
Le dossier `presentation` est responsable de l'**interface utilisateur** de la feature. Il contient les composants UI et le fonctions de state management.
|
|
85
|
+
|
|
86
|
+
Il se découpe de la manière suivante :
|
|
87
|
+
|
|
88
|
+
- `components` :
|
|
89
|
+
- Contient les composants UI de la feature.
|
|
90
|
+
- `hooks` :
|
|
91
|
+
- Contient les hooks de la feature.
|
|
92
|
+
|
|
93
|
+
### Injection de dépendances
|
|
94
|
+
|
|
95
|
+
**L’injection de dépendances** (DI) est un principe clé dans cette architecture, utilisé pour découpler les composants et rendre le code plus **modulaire**, **testable** et **maintenable**.
|
|
96
|
+
|
|
97
|
+
[Inversify](https://github.com/inversify/InversifyJS) est utilisé pour gérer la DI. Cela signifie que les dépendances (comme les _repositories_, _datasources_, ou _usecases_) ne sont pas instanciées directement dans les classes qui les utilisent, mais injectées depuis un conteneur central.
|
|
98
|
+
|
|
99
|
+
Par exemple, un **usecase** de la couche **domain** n’instancie pas lui-même un repository : il le reçoit via son constructeur, ce qui permet de facilement remplacer une implémentation par une autre (ex : un mock pour les tests).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Ressources utiles
|
|
104
|
+
|
|
105
|
+
- [Article de Yield Studio sur la Clean Architecture](https://www.yieldstudio.fr/blog/clean-architecture-comment-structurer-votre-app-pour-eviter-la-dette-demain) : les concepts abordés reflètent bien le fonctionnement choisi sur ce projet, bien que pas 100% identique et nécessairement adapté à React Native.
|
|
106
|
+
- [Explication des principes SOLID](https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design#single-responsibility-principle)
|
|
107
|
+
- [TDD Clean Architecture par Resocoder](https://resocoder.com/flutter-clean-architecture-tdd/) : base sur laquelle s'est largement inspiré le projet. Le TDD porte cependant sur un projet Flutter, mais les concepts sont identiques.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_SLUG}}",
|
|
3
|
+
"main": "expo-router/entry",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "expo start",
|
|
7
|
+
"clean": "rm -rf ios android",
|
|
8
|
+
"android": "expo run:android",
|
|
9
|
+
"ios": "expo run:ios",
|
|
10
|
+
"web": "expo start --web",
|
|
11
|
+
"test": "jest --watchAll",
|
|
12
|
+
"lint": "expo lint"
|
|
13
|
+
},
|
|
14
|
+
"jest": {
|
|
15
|
+
"preset": "jest-expo",
|
|
16
|
+
"collectCoverage": true,
|
|
17
|
+
"setupFiles": [
|
|
18
|
+
"./tests/setup.ts"
|
|
19
|
+
],
|
|
20
|
+
"collectCoverageFrom": [
|
|
21
|
+
"src/**/*.{ts,tsx}",
|
|
22
|
+
"!src/**/*.d.ts",
|
|
23
|
+
"!src/**/*.stories.{ts,tsx}",
|
|
24
|
+
"!src/**/*.test.{ts,tsx}",
|
|
25
|
+
"!src/**/index.{ts,tsx}"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@expo/vector-icons": "^14.1.0",
|
|
30
|
+
"@react-native-async-storage/async-storage": "2.1.2",
|
|
31
|
+
"@react-native-firebase/app": "^22.2.0",
|
|
32
|
+
"@react-native-firebase/crashlytics": "^22.2.0",
|
|
33
|
+
"@react-navigation/bottom-tabs": "^7.3.12",
|
|
34
|
+
"@react-navigation/native": "^7.1.8",
|
|
35
|
+
"@tanstack/react-query": "^5.75.2",
|
|
36
|
+
"expo": "^53.0.7",
|
|
37
|
+
"expo-blur": "~14.1.4",
|
|
38
|
+
"expo-constants": "~17.1.5",
|
|
39
|
+
"expo-font": "~13.3.1",
|
|
40
|
+
"expo-haptics": "~14.1.4",
|
|
41
|
+
"expo-linking": "~7.1.4",
|
|
42
|
+
"expo-network": "~7.1.5",
|
|
43
|
+
"expo-router": "~5.0.5",
|
|
44
|
+
"expo-splash-screen": "~0.30.8",
|
|
45
|
+
"expo-status-bar": "~2.2.3",
|
|
46
|
+
"expo-symbols": "~0.4.4",
|
|
47
|
+
"expo-system-ui": "~5.0.7",
|
|
48
|
+
"expo-web-browser": "~14.1.6",
|
|
49
|
+
"i18next": "^25.0.2",
|
|
50
|
+
"inversify": "^7.5.1",
|
|
51
|
+
"react": "19.0.0",
|
|
52
|
+
"react-dom": "19.0.0",
|
|
53
|
+
"react-i18next": "^15.5.1",
|
|
54
|
+
"react-native": "0.79.2",
|
|
55
|
+
"react-native-gesture-handler": "~2.25.0",
|
|
56
|
+
"react-native-json-tree": "^1.5.0",
|
|
57
|
+
"react-native-reanimated": "~3.17.5",
|
|
58
|
+
"react-native-safe-area-context": "5.4.0",
|
|
59
|
+
"react-native-screens": "~4.10.0",
|
|
60
|
+
"react-native-web": "^0.20.0",
|
|
61
|
+
"react-native-webview": "13.13.5",
|
|
62
|
+
"zustand": "^5.0.4"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@babel/core": "^7.27.1",
|
|
66
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
67
|
+
"@types/jest": "^29.5.14",
|
|
68
|
+
"@types/react": "~19.1.2",
|
|
69
|
+
"babel-plugin-transform-typescript-metadata": "^0.3.2",
|
|
70
|
+
"jest": "^29.7.0",
|
|
71
|
+
"jest-expo": "~53.0.4",
|
|
72
|
+
"reflect-metadata": "^0.2.2",
|
|
73
|
+
"ts-node": "^10.9.2",
|
|
74
|
+
"typescript": "^5.8.3",
|
|
75
|
+
"expo-build-properties": "~0.14.6"
|
|
76
|
+
},
|
|
77
|
+
"private": true
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script to clean empty files and directories in the src directory
|
|
4
|
+
# Usage: ./clean-src.sh
|
|
5
|
+
|
|
6
|
+
# Exit on any error
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# Colors for output
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Check if src directory exists
|
|
16
|
+
if [ ! -d "src" ]; then
|
|
17
|
+
echo -e "${RED}Error: src directory not found${NC}"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo -e "${YELLOW}Starting cleanup of empty files and directories in src/...${NC}"
|
|
22
|
+
|
|
23
|
+
# Count of removed items
|
|
24
|
+
removed_files=0
|
|
25
|
+
removed_dirs=0
|
|
26
|
+
|
|
27
|
+
# Find and remove empty files
|
|
28
|
+
while IFS= read -r -d '' file; do
|
|
29
|
+
if [ ! -s "$file" ]; then
|
|
30
|
+
echo "Removing empty file: $file"
|
|
31
|
+
rm "$file"
|
|
32
|
+
((removed_files++))
|
|
33
|
+
fi
|
|
34
|
+
done < <(find src -type f -print0)
|
|
35
|
+
|
|
36
|
+
# Find and remove empty directories (from deepest to root)
|
|
37
|
+
while IFS= read -r -d '' dir; do
|
|
38
|
+
if [ -z "$(ls -A "$dir")" ]; then
|
|
39
|
+
echo "Removing empty directory: $dir"
|
|
40
|
+
rmdir "$dir"
|
|
41
|
+
((removed_dirs++))
|
|
42
|
+
fi
|
|
43
|
+
done < <(find src -type d -depth -print0)
|
|
44
|
+
|
|
45
|
+
# Print summary
|
|
46
|
+
echo -e "\n${GREEN}Cleanup completed!${NC}"
|
|
47
|
+
echo -e "Removed ${removed_files} empty files"
|
|
48
|
+
echo -e "Removed ${removed_dirs} empty directories"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script de génération d'une nouvelle feature
|
|
4
|
+
|
|
5
|
+
# Prompt pour demander à l'utilisateur le nom de la feature à créer
|
|
6
|
+
read -p "Entrez le nom de la feature à créer : " feature_name
|
|
7
|
+
|
|
8
|
+
# Convertir le nom en PascalCase pour les noms de fichiers
|
|
9
|
+
feature_name_pascal=$(echo "$feature_name" | sed -E 's/(^|_)([a-z])/\U\2/g')
|
|
10
|
+
|
|
11
|
+
# Vérifier si la feature existe déjà
|
|
12
|
+
if [ -d "src/features/$feature_name" ]; then
|
|
13
|
+
echo "❌ La feature '$feature_name' existe déjà !"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Créer le dossier principal de la feature
|
|
18
|
+
mkdir -p "src/features/$feature_name"
|
|
19
|
+
|
|
20
|
+
# Créer les dossiers et fichiers de la feature
|
|
21
|
+
directories=(
|
|
22
|
+
"@types"
|
|
23
|
+
"data/datasources"
|
|
24
|
+
"data/repositories"
|
|
25
|
+
"data/models"
|
|
26
|
+
"domain/entities"
|
|
27
|
+
"domain/usecases"
|
|
28
|
+
"domain/repositories"
|
|
29
|
+
"presentation/pages"
|
|
30
|
+
"presentation/components"
|
|
31
|
+
"presentation/hooks"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Créer tous les dossiers
|
|
35
|
+
for dir in "${directories[@]}"; do
|
|
36
|
+
mkdir -p "src/features/$feature_name/$dir"
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
echo "✅ Feature '$feature_name' créée avec succès !"
|
|
40
|
+
echo "📁 Structure créée dans src/features/$feature_name"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Tabs } from "expo-router";
|
|
2
|
+
import { AppIcon } from "@/core/design/components/app-icon";
|
|
3
|
+
import { useSearchMovies } from "@/features/movies/presentation/hooks/use-search-movies";
|
|
4
|
+
|
|
5
|
+
export default function TabsLayout() {
|
|
6
|
+
const { mutate: searchMovies } = useSearchMovies();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Tabs>
|
|
10
|
+
<Tabs.Screen
|
|
11
|
+
name="home"
|
|
12
|
+
options={({ route }) => ({
|
|
13
|
+
title: "Accueil",
|
|
14
|
+
tabBarIcon: ({ focused, color }) => (
|
|
15
|
+
<AppIcon name={focused ? "home" : "home-outline"} color={color} />
|
|
16
|
+
),
|
|
17
|
+
})}
|
|
18
|
+
/>
|
|
19
|
+
<Tabs.Screen
|
|
20
|
+
name="favorites"
|
|
21
|
+
options={({ route }) => ({
|
|
22
|
+
title: "Favoris",
|
|
23
|
+
tabBarIcon: ({ focused, color }) => (
|
|
24
|
+
<AppIcon name={focused ? "star" : "star-outline"} color={color} />
|
|
25
|
+
),
|
|
26
|
+
})}
|
|
27
|
+
/>
|
|
28
|
+
<Tabs.Screen
|
|
29
|
+
name="settings"
|
|
30
|
+
options={({ route }) => ({
|
|
31
|
+
title: "Paramètres",
|
|
32
|
+
tabBarIcon: ({ focused, color }) => (
|
|
33
|
+
<AppIcon
|
|
34
|
+
name={focused ? "settings" : "settings-outline"}
|
|
35
|
+
color={color}
|
|
36
|
+
/>
|
|
37
|
+
),
|
|
38
|
+
})}
|
|
39
|
+
/>
|
|
40
|
+
</Tabs>
|
|
41
|
+
);
|
|
42
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/favorites.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import { StyleSheet, FlatList, TouchableOpacity, Alert } from "react-native";
|
|
3
|
+
import { useNavigation } from "expo-router";
|
|
4
|
+
|
|
5
|
+
import { AppSeparator } from "@/core/design/components/app-separator";
|
|
6
|
+
import { AppSpacing } from "@/core/design/theme/app-sizes";
|
|
7
|
+
import { useAppStyles } from "@/core/design/hooks/use-app-styles";
|
|
8
|
+
import { useFavorites } from "@/features/favorites/presentation/hooks/use-favorites";
|
|
9
|
+
import { FavoritesCard } from "@/features/favorites/presentation/components/favorites-card";
|
|
10
|
+
import { useClearFavorites } from "@/features/favorites/presentation/hooks/use-clear-favorites";
|
|
11
|
+
import { AppIcon } from "@/core/design/components/app-icon";
|
|
12
|
+
import { Colors } from "react-native/Libraries/NewAppScreen";
|
|
13
|
+
import { useAppColorScheme } from "@/core/design/hooks/use-app-color-scheme";
|
|
14
|
+
|
|
15
|
+
export default function FavoritesPage() {
|
|
16
|
+
const navigation = useNavigation();
|
|
17
|
+
|
|
18
|
+
const styles = useAppStyles(createStyles);
|
|
19
|
+
const { colorScheme } = useAppColorScheme();
|
|
20
|
+
const { data: favorites } = useFavorites();
|
|
21
|
+
const { mutate: clearFavorites } = useClearFavorites();
|
|
22
|
+
|
|
23
|
+
const confirmAndClearFavorites = () => {
|
|
24
|
+
Alert.alert(
|
|
25
|
+
"Supprimer tous les favoris",
|
|
26
|
+
"Voulez-vous vraiment supprimer tous les favoris ?",
|
|
27
|
+
[
|
|
28
|
+
{ text: "Annuler", style: "cancel" },
|
|
29
|
+
{
|
|
30
|
+
text: "Supprimer",
|
|
31
|
+
style: "destructive",
|
|
32
|
+
onPress: () => clearFavorites(),
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
navigation.setOptions({
|
|
40
|
+
headerRight: () => (
|
|
41
|
+
<TouchableOpacity
|
|
42
|
+
onPress={confirmAndClearFavorites}
|
|
43
|
+
style={{ marginRight: AppSpacing.medium }}
|
|
44
|
+
>
|
|
45
|
+
<AppIcon name="trash-outline" size={24} color={colorScheme.danger} />
|
|
46
|
+
</TouchableOpacity>
|
|
47
|
+
),
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<FlatList
|
|
53
|
+
contentContainerStyle={styles.container}
|
|
54
|
+
style={styles.list}
|
|
55
|
+
ItemSeparatorComponent={() => <AppSeparator />}
|
|
56
|
+
data={favorites}
|
|
57
|
+
renderItem={({ item }) => <FavoritesCard movie={item} />}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createStyles() {
|
|
63
|
+
return StyleSheet.create({
|
|
64
|
+
container: {
|
|
65
|
+
padding: AppSpacing.medium,
|
|
66
|
+
},
|
|
67
|
+
list: {
|
|
68
|
+
flex: 1,
|
|
69
|
+
width: "100%",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Button,
|
|
5
|
+
FlatList,
|
|
6
|
+
NativeSyntheticEvent,
|
|
7
|
+
RefreshControl,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Text,
|
|
10
|
+
TextInputSubmitEditingEventData,
|
|
11
|
+
View,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { useNavigation } from "expo-router";
|
|
14
|
+
|
|
15
|
+
import { useAppStyles } from "@/core/design/hooks/use-app-styles";
|
|
16
|
+
import { AppSpacing } from "@/core/design/theme/app-sizes";
|
|
17
|
+
import { AppFontSize } from "@/core/design/theme/app-fonts";
|
|
18
|
+
import { ColorScheme } from "@/core/design/@types/color-scheme";
|
|
19
|
+
import { useRandomMovies } from "@/features/movies/presentation/hooks/use-random-movies";
|
|
20
|
+
import { MovieTile } from "@/features/movies/presentation/components/movie-tile";
|
|
21
|
+
import { useSearchMovies } from "@/features/movies/presentation/hooks/use-search-movies";
|
|
22
|
+
|
|
23
|
+
export default function HomePage() {
|
|
24
|
+
const navigation = useNavigation();
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
data: randomMovies,
|
|
28
|
+
isLoading: isLoadingRandomMovies,
|
|
29
|
+
isError: isRandomMoviesError,
|
|
30
|
+
refetch: refetchRandomMovies,
|
|
31
|
+
isRefetching: isRefetchingRandomMovies,
|
|
32
|
+
} = useRandomMovies();
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
mutate: searchMovies,
|
|
36
|
+
data: searchResults,
|
|
37
|
+
isPending: isSearching,
|
|
38
|
+
isError: isSearchError,
|
|
39
|
+
variables,
|
|
40
|
+
reset: resetSearch,
|
|
41
|
+
} = useSearchMovies();
|
|
42
|
+
|
|
43
|
+
const styles = useAppStyles(createStyles);
|
|
44
|
+
|
|
45
|
+
useLayoutEffect(() => {
|
|
46
|
+
navigation.setOptions({
|
|
47
|
+
headerSearchBarOptions: {
|
|
48
|
+
placeholder: "Rechercher un film",
|
|
49
|
+
onSubmitEditing: (
|
|
50
|
+
e: NativeSyntheticEvent<TextInputSubmitEditingEventData>
|
|
51
|
+
) => {
|
|
52
|
+
searchMovies(e.nativeEvent.text);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}, [searchResults]);
|
|
57
|
+
|
|
58
|
+
if (isLoadingRandomMovies || isSearching) {
|
|
59
|
+
return (
|
|
60
|
+
<View style={styles.container}>
|
|
61
|
+
<ActivityIndicator size="large" />
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isRandomMoviesError || isSearchError) {
|
|
67
|
+
return (
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<Text>Error</Text>
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<FlatList
|
|
76
|
+
ListHeaderComponent={() =>
|
|
77
|
+
searchResults?.length &&
|
|
78
|
+
searchResults.length > 0 && (
|
|
79
|
+
<View style={styles.searchResultsHeader}>
|
|
80
|
+
<Text style={styles.searchResultsHeaderText}>
|
|
81
|
+
{searchResults.length} résultat
|
|
82
|
+
{searchResults.length > 1 ? "s" : ""} pour : "{variables}"
|
|
83
|
+
</Text>
|
|
84
|
+
<Button title="Effacer" onPress={resetSearch} />
|
|
85
|
+
</View>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
refreshControl={
|
|
89
|
+
<RefreshControl
|
|
90
|
+
onRefresh={refetchRandomMovies}
|
|
91
|
+
refreshing={isRefetchingRandomMovies}
|
|
92
|
+
/>
|
|
93
|
+
}
|
|
94
|
+
numColumns={3}
|
|
95
|
+
data={searchResults || randomMovies}
|
|
96
|
+
renderItem={({ item }) => <MovieTile movie={item} />}
|
|
97
|
+
keyExtractor={(item) => item.id}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const createStyles = (colorScheme: ColorScheme) =>
|
|
103
|
+
StyleSheet.create({
|
|
104
|
+
container: {
|
|
105
|
+
flex: 1,
|
|
106
|
+
justifyContent: "center",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
},
|
|
109
|
+
flatList: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
},
|
|
112
|
+
searchResultsHeader: {
|
|
113
|
+
padding: AppSpacing.medium,
|
|
114
|
+
flexDirection: "row",
|
|
115
|
+
justifyContent: "space-between",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
},
|
|
118
|
+
searchResultsHeaderText: {
|
|
119
|
+
fontSize: AppFontSize.small,
|
|
120
|
+
color: colorScheme.textSecondary,
|
|
121
|
+
},
|
|
122
|
+
});
|
package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/index.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Button, StyleSheet, View } from "react-native";
|
|
2
|
+
import { useSession } from "@/features/auth/presentation/hooks/use-session";
|
|
3
|
+
import { useAppStyles } from "@/core/design/hooks/use-app-styles";
|
|
4
|
+
import { router } from "expo-router";
|
|
5
|
+
import { AppSeparator } from "@/core/design/components/app-separator";
|
|
6
|
+
|
|
7
|
+
export default function SettingsPage() {
|
|
8
|
+
const { logout } = useSession();
|
|
9
|
+
|
|
10
|
+
const styles = useAppStyles(createStyles);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View style={styles.container}>
|
|
14
|
+
<Button title="Profil" onPress={() => router.push("/settings/profile")} />
|
|
15
|
+
<AppSeparator />
|
|
16
|
+
<Button title="Déconnexion" onPress={logout} />
|
|
17
|
+
</View>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createStyles() {
|
|
22
|
+
return StyleSheet.create({
|
|
23
|
+
container: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
justifyContent: "center",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
2
|
+
import { useAppStyles } from "@/core/design/hooks/use-app-styles";
|
|
3
|
+
|
|
4
|
+
export default function ProfilePage() {
|
|
5
|
+
const styles = useAppStyles(createStyles);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<View style={styles.container}>
|
|
9
|
+
<Text>Profile</Text>
|
|
10
|
+
</View>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createStyles() {
|
|
15
|
+
return StyleSheet.create({
|
|
16
|
+
container: {
|
|
17
|
+
flex: 1,
|
|
18
|
+
justifyContent: "center",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Redirect, Stack } from "expo-router";
|
|
2
|
+
import { useSession } from "@/features/auth/presentation/hooks/use-session";
|
|
3
|
+
|
|
4
|
+
export default function ProtectedLayout() {
|
|
5
|
+
const { isAuthenticated } = useSession();
|
|
6
|
+
|
|
7
|
+
if (!isAuthenticated()) {
|
|
8
|
+
return <Redirect href="/login" />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Stack>
|
|
13
|
+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
14
|
+
<Stack.Screen
|
|
15
|
+
name="details"
|
|
16
|
+
options={{ headerShown: false, presentation: "modal" }}
|
|
17
|
+
/>
|
|
18
|
+
</Stack>
|
|
19
|
+
);
|
|
20
|
+
}
|