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.
Files changed (111) hide show
  1. package/bin/cli.js +9 -13
  2. package/bin/commands/create-project.js +106 -0
  3. package/bin/commands/translate.js +12 -0
  4. package/bin/config/templates.js +9 -0
  5. package/bin/utils/file-utils.js +84 -0
  6. package/bin/utils/project-utils.js +49 -0
  7. package/package.json +4 -2
  8. package/templates/react-native/expo-clean-architecture/README.md +154 -0
  9. package/templates/react-native/expo-clean-architecture/app.config.ts +61 -0
  10. package/templates/react-native/expo-clean-architecture/assets/config/.gitkeep +3 -0
  11. package/templates/react-native/expo-clean-architecture/assets/fonts/SpaceMono-Regular.ttf +0 -0
  12. package/templates/react-native/expo-clean-architecture/assets/images/adaptive-icon.png +0 -0
  13. package/templates/react-native/expo-clean-architecture/assets/images/favicon.png +0 -0
  14. package/templates/react-native/expo-clean-architecture/assets/images/icon.png +0 -0
  15. package/templates/react-native/expo-clean-architecture/assets/images/partial-react-logo.png +0 -0
  16. package/templates/react-native/expo-clean-architecture/assets/images/react-logo.png +0 -0
  17. package/templates/react-native/expo-clean-architecture/assets/images/react-logo@2x.png +0 -0
  18. package/templates/react-native/expo-clean-architecture/assets/images/react-logo@3x.png +0 -0
  19. package/templates/react-native/expo-clean-architecture/assets/images/splash-icon.png +0 -0
  20. package/templates/react-native/expo-clean-architecture/babel.config.js +11 -0
  21. package/templates/react-native/expo-clean-architecture/docs/00-introduction.md +3 -0
  22. package/templates/react-native/expo-clean-architecture/docs/01-architecture.md +107 -0
  23. package/templates/react-native/expo-clean-architecture/package.json +78 -0
  24. package/templates/react-native/expo-clean-architecture/scripts/clean-src.sh +48 -0
  25. package/templates/react-native/expo-clean-architecture/scripts/generate-feature.sh +40 -0
  26. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/_layout.tsx +42 -0
  27. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/favorites.tsx +72 -0
  28. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/home.tsx +122 -0
  29. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/_layout.tsx +5 -0
  30. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/index.tsx +29 -0
  31. package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/profile.tsx +22 -0
  32. package/templates/react-native/expo-clean-architecture/src/app/(protected)/_layout.tsx +20 -0
  33. package/templates/react-native/expo-clean-architecture/src/app/(protected)/details.tsx +124 -0
  34. package/templates/react-native/expo-clean-architecture/src/app/(public)/_layout.tsx +18 -0
  35. package/templates/react-native/expo-clean-architecture/src/app/(public)/login.tsx +31 -0
  36. package/templates/react-native/expo-clean-architecture/src/app/_layout.tsx +33 -0
  37. package/templates/react-native/expo-clean-architecture/src/app/index.tsx +8 -0
  38. package/templates/react-native/expo-clean-architecture/src/core/constants/api-constants.ts +10 -0
  39. package/templates/react-native/expo-clean-architecture/src/core/constants/image-constants.ts +3 -0
  40. package/templates/react-native/expo-clean-architecture/src/core/constants/query-keys.ts +6 -0
  41. package/templates/react-native/expo-clean-architecture/src/core/constants/storage-keys.ts +3 -0
  42. package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme-state.ts +35 -0
  43. package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme.ts +12 -0
  44. package/templates/react-native/expo-clean-architecture/src/core/design/components/app-icon.tsx +16 -0
  45. package/templates/react-native/expo-clean-architecture/src/core/design/components/app-separator.tsx +26 -0
  46. package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-color-scheme.ts +52 -0
  47. package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-fonts.ts +12 -0
  48. package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-styles.ts +28 -0
  49. package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-colors.ts +21 -0
  50. package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-fonts.ts +16 -0
  51. package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-sizes.ts +14 -0
  52. package/templates/react-native/expo-clean-architecture/src/core/di/injection-container.ts +53 -0
  53. package/templates/react-native/expo-clean-architecture/src/core/errors/index.ts +1 -0
  54. package/templates/react-native/expo-clean-architecture/src/core/helpers/@types.ts +23 -0
  55. package/templates/react-native/expo-clean-architecture/src/core/helpers/rest-client.ts +144 -0
  56. package/templates/react-native/expo-clean-architecture/src/core/helpers/result.ts +37 -0
  57. package/templates/react-native/expo-clean-architecture/src/core/helpers/usecase.ts +5 -0
  58. package/templates/react-native/expo-clean-architecture/src/core/hooks/use-network.ts +18 -0
  59. package/templates/react-native/expo-clean-architecture/src/core/i18n/@types/i18next.d.ts +11 -0
  60. package/templates/react-native/expo-clean-architecture/src/core/i18n/index.ts +19 -0
  61. package/templates/react-native/expo-clean-architecture/src/core/i18n/translations/fr.json +12 -0
  62. package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service-impl.ts +29 -0
  63. package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service.ts +26 -0
  64. package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service-impl.ts +15 -0
  65. package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service.ts +13 -0
  66. package/templates/react-native/expo-clean-architecture/src/core/services/network-service-impl.ts +40 -0
  67. package/templates/react-native/expo-clean-architecture/src/core/services/network-service.ts +16 -0
  68. package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-state.ts +38 -0
  69. package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-status-enum.ts +16 -0
  70. package/templates/react-native/expo-clean-architecture/src/features/auth/presentation/hooks/use-session.ts +18 -0
  71. package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource-impl.ts +25 -0
  72. package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource.ts +5 -0
  73. package/templates/react-native/expo-clean-architecture/src/features/favorites/data/repositories/favorites-repository-impl.ts +46 -0
  74. package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/repositories/favorites-repository.ts +8 -0
  75. package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/add-to-favorites-usecase.ts +23 -0
  76. package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/clear-favorites-usecase.ts +23 -0
  77. package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/get-favorites-usecase.ts +24 -0
  78. package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/remove-from-favorites-usecase.ts +23 -0
  79. package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/components/favorites-card.tsx +77 -0
  80. package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-add-favorite.ts +24 -0
  81. package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-clear-favorites.ts +22 -0
  82. package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-favorites.ts +22 -0
  83. package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-remove-favorite.ts +24 -0
  84. package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource-impl.ts +50 -0
  85. package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource.ts +8 -0
  86. package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-details-model.ts +67 -0
  87. package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-model.ts +30 -0
  88. package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/tmdb-response-model.ts +6 -0
  89. package/templates/react-native/expo-clean-architecture/src/features/movies/data/repositories/movies-repository-impl.ts +34 -0
  90. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-details-entity.ts +28 -0
  91. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-entity.ts +6 -0
  92. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/repositories/movies-repository.ts +25 -0
  93. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-movie-details-usecase.ts +26 -0
  94. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-random-movies-usecase.ts +24 -0
  95. package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/search-movies-usecase.ts +23 -0
  96. package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/components/movie-tile.tsx +69 -0
  97. package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-movie-details.ts +22 -0
  98. package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-random-movies.ts +22 -0
  99. package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-search-movies.ts +22 -0
  100. package/templates/react-native/expo-clean-architecture/tests/core/services/local-storage-service-impl.test.ts +108 -0
  101. package/templates/react-native/expo-clean-architecture/tests/core/services/monitoring-service.test.ts +74 -0
  102. package/templates/react-native/expo-clean-architecture/tests/core/services/network-service.test.ts +117 -0
  103. package/templates/react-native/expo-clean-architecture/tests/features/auth/presentation/hooks/use-session.test.ts +69 -0
  104. package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/datasources/favorites-datasource.test.ts +69 -0
  105. package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/repositories/favorites-repository-impl.test.ts +124 -0
  106. package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/add-to-favorites-usecase.test.ts +54 -0
  107. package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/clear-favorites-usecase.test.ts +44 -0
  108. package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/get-favorites-usecase.test.ts +74 -0
  109. package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/remove-from-favorites-usecase.test.ts +52 -0
  110. package/templates/react-native/expo-clean-architecture/tests/setup.ts +9 -0
  111. 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
+ }
@@ -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
+ });
@@ -0,0 +1,5 @@
1
+ import { Stack } from "expo-router";
2
+
3
+ export default function SettingsLayout() {
4
+ return <Stack screenOptions={{ headerShown: false }} />;
5
+ }
@@ -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
+ }