


De zéro à vitrine?: mon parcours pour créer une plateforme de location de propriétés
Nov 11, 2024 am 02:10 AMContenu
- Présentation
- Pile technologique
- Aper?u rapide
- API
- Frontend
- Application mobile
- Tableau de bord d'administration
- Points d'intérêt
- Ressources
Code source?: https://github.com/aelassas/movinin
Démo?: https://movinin.dynv6.net:3004
Introduction
L'idée est née d'une envie de construire sans frontières – une plateforme de location immobilière entièrement personnalisable et opérationnelle où chaque aspect est sous votre contr?le?:
- Possédez l'UI/UX?: Concevez des expériences client uniques sans lutter contre les limitations des modèles
- Contr?lez le backend?: implémentez une logique métier personnalisée et des structures de données qui correspondent parfaitement aux exigences
- Ma?trisez DevOps?: Déployez, faites évoluer et surveillez l'application avec les outils et flux de travail préférés
- Extendez librement?: ajoutez de nouvelles fonctionnalités et intégrations sans contraintes de plate-forme ni frais supplémentaires
Pile technologique
Voici la pile technologique qui a rendu cela possible?:
- TypeScript
- Node.js
- MongoDB
- Réagir
- MUI
- Expo
- Rayure
- Docker
Une décision clé en matière de conception a été prise d'utiliser TypeScript en raison de ses nombreux avantages. TypeScript offre un typage, des outils et une intégration puissants, ce qui donne lieu à un code de haute qualité, évolutif, plus lisible et maintenable, facile à déboguer et à tester.
J'ai choisi React pour ses puissantes capacités de rendu, MongoDB pour une modélisation flexible des données et Stripe pour le traitement sécurisé des paiements.
En choisissant cette pile, vous ne vous contentez pas de créer un site Web et une application mobile : vous investissez dans une fondation qui peut évoluer avec vos besoins, soutenue par des technologies open source robustes et une communauté de développeurs croissante.
Aper?u rapide
Dans cette section, vous verrez les pages principales du frontend, le tableau de bord d'administration et l'application mobile.
L'extrémité avant
Depuis le frontend, le client peut rechercher les propriétés disponibles, choisir une propriété et payer.
Ci-dessous se trouve la page principale du frontend où le client peut indiquer un point et une heure de localisation et rechercher des propriétés disponibles.
Vous trouverez ci-dessous le résultat de recherche de la page principale où le client peut choisir un bien à louer.
Ci-dessous se trouve la page où le client peut consulter les détails de la propriété?:
Ci-dessous une vue des images de la propriété :
Vous trouverez ci-dessous la page de paiement où le client peut définir les options de location et le paiement. Si le client n'est pas inscrit, il peut payer et s'inscrire en même temps. Il recevra un email de confirmation et d'activation pour définir son mot de passe s'il n'est pas encore inscrit.
Vous trouverez ci-dessous la page de connexion. En production, les cookies d'authentification sont httpOnly, signés, sécurisés et stricts sur site. Ces options empêchent les attaques XSS, CSRF et MITM. Les cookies d'authentification sont également protégés contre les attaques XST via un middleware personnalisé.
Vous trouverez ci-dessous la page d'inscription.
Ci-dessous se trouve la page où le client peut voir et gérer ses réservations.
Ci-dessous se trouve la page où le client peut voir une réservation en détail.
Ci-dessous se trouve la page où le client peut voir ses notifications.
Ci-dessous se trouve la page où le client peut gérer ses paramètres.
Ci-dessous la page où le client peut modifier son mot de passe.
C'est tout. Ce sont les pages principales du frontend.
Tableau de bord d'administration
Trois types d'utilisateurs?:
- Administrateurs?: ils ont un accès complet au tableau de bord d'administration. Ils peuvent tout faire.
- Agences?: elles ont un accès limité au tableau de bord d'administration. Ils ne peuvent gérer que leurs propriétés, leurs réservations et leurs clients.
- Clients?: ils ont accès uniquement au frontend et à l'application mobile. Ils ne peuvent pas accéder au tableau de bord d'administration.
La plateforme est con?ue pour fonctionner avec plusieurs agences. Chaque agence peut gérer ses propriétés, ses clients et ses réservations depuis le tableau de bord d'administration. La plateforme peut également fonctionner avec une seule agence.
Depuis le backend, les administrateurs peuvent créer et gérer des agences, des propriétés, des emplacements, des clients et des réservations.
Lorsque de nouvelles agences sont créées, elles re?oivent un e-mail les invitant à créer leur compte pour accéder au tableau de bord d'administration afin de pouvoir gérer leurs propriétés, leurs clients et leurs réservations.
Vous trouverez ci-dessous la page de connexion du tableau de bord d'administration.
Vous trouverez ci-dessous la page du tableau de bord où les administrateurs et les agences peuvent voir et gérer les réservations.
Si le statut d'une réservation change, le client concerné recevra une notification et un e-mail.
Ci-dessous se trouve la page où les propriétés sont affichées et peuvent être gérées.
Vous trouverez ci-dessous la page sur laquelle les administrateurs et les agences peuvent créer de nouvelles propriétés en fournissant des images et des informations sur la propriété. Pour une annulation gratuite, réglez-le à 0. Sinon, fixez le prix de l'option ou laissez-le vide si vous ne souhaitez pas l'inclure.
Vous trouverez ci-dessous la page sur laquelle les administrateurs et les agences peuvent modifier les propriétés.
Vous trouverez ci-dessous la page où les administrateurs peuvent gérer les clients.
Ci-dessous se trouve la page où créer des réservations si l'agence souhaite créer une réservation à partir du tableau de bord d'administration. Sinon, les réservations sont créées automatiquement lorsque le processus de paiement est terminé depuis le frontend ou l'application mobile.
Vous trouverez ci-dessous la page où modifier les réservations.
Ci-dessous se trouve la page où gérer les agences.
Ci-dessous se trouve la page où créer de nouvelles agences.
Vous trouverez ci-dessous la page où modifier les agences.
Vous trouverez ci-dessous la page où voir les propriétés des agences.
Ci-dessous se trouve la page où voir les réservations des clients.
Vous trouverez ci-dessous la page sur laquelle les administrateurs et les agences peuvent gérer leurs paramètres.
Il existe d'autres pages mais ce sont les pages principales du tableau de bord d'administration.
C'est tout. Ce sont les pages principales du tableau de bord d'administration.
API
L'API expose toutes les fonctions nécessaires au tableau de bord d'administration, au frontend et à l'application mobile. L'API suit le modèle de conception MVC. JWT est utilisé pour l'authentification. Certaines fonctions nécessitent une authentification, telles que les fonctions liées à la gestion des propriétés, des réservations et des clients, et d'autres ne nécessitent pas d'authentification, telles que la récupération des emplacements et des propriétés disponibles pour les utilisateurs non authentifiés?:
- Le dossier ./api/src/models/ contient des modèles MongoDB.
- Le dossier ./api/src/routes/ contient les routes Express.
- Le dossier ./api/src/controllers/ contient des contr?leurs.
- Le dossier ./api/src/middlewares/ contient des middlewares.
- ./api/src/config/env.config.ts contient la configuration et les définitions de type TypeScript.
- Le dossier ./api/src/lang/ contient la localisation.
- ./api/src/app.ts est le serveur principal sur lequel les routes sont chargées.
- ./api/index.ts est le point d'entrée principal de l'API.
index.ts est le point d'entrée principal de l'API?:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Il s'agit d'un fichier TypeScript qui démarre un serveur à l'aide de Node.js et Express. Il importe plusieurs modules, notamment dotenv, process, fs, http, https, mongoose et app. Il vérifie ensuite si la variable d'environnement HTTPS est définie sur true et, si tel est le cas, crée un serveur HTTPS à l'aide du module https ainsi que de la clé privée et du certificat fournis. Sinon, il crée un serveur HTTP à l'aide du module http. Le serveur écoute sur le port spécifié dans la variable d'environnement PORT.
La fonction close est définie pour arrêter gracieusement le serveur lorsqu'un signal de terminaison est re?u. Il ferme le serveur et la connexion MongoDB, puis quitte le processus avec un code d'état de 0. Enfin, il enregistre la fonction de fermeture à appeler lorsque le processus re?oit un signal SIGINT, SIGTERM ou SIGQUIT.
app.ts est le point d'entrée principal de l'API?:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Tout d'abord, nous récupérons la cha?ne de connexion MongoDB, puis nous établissons une connexion avec la base de données MongoDB. Ensuite, nous créons une application Express et chargeons des middlewares tels que cors, compression, casque et nocache. Nous avons mis en place diverses mesures de sécurité à l'aide de la bibliothèque middleware du casque. nous importons également divers fichiers d'itinéraire pour différentes parties de l'application telles que supplierRoutes, bookingRoutes, locationRoutes, notificationRoutes, propertyRoutes et userRoutes. Enfin, nous chargeons les itinéraires Express et exportons l'application.
Il y a 8 routes dans l'API. Chaque route possède son propre contr?leur suivant le modèle de conception MVC et les principes SOLID. Ci-dessous les principaux itinéraires?:
- userRoutes?: fournit des fonctions REST liées aux utilisateurs
- agencyRoutes?: Fournit des fonctions REST liées aux agences
- countryRoutes?: fournit des fonctions REST liées aux pays
- locationRoutes?: fournit des fonctions REST liées aux emplacements
- propertyRoutes?: fournit des fonctions REST liées aux propriétés
- bookingRoutes?: fournit des fonctions REST liées aux réservations
- notificationRoutes?: Fournit des fonctions REST liées aux notifications
- stripeRoutes?: fournit des fonctions REST liées à la passerelle de paiement Stripe
Nous n'allons pas expliquer chaque itinéraire un par un. Prenons, par exemple, propertyRoutes et voyons comment il a été réalisé. Vous pouvez parcourir le code source et voir tous les itinéraires.
Voici propertyRoutes.ts?:
import express from 'express' import multer from 'multer' import routeNames from '../config/propertyRoutes.config' import authJwt from '../middlewares/authJwt' import * as propertyController from '../controllers/propertyController' const routes = express.Router() routes.route(routeNames.create).post(authJwt.verifyToken, propertyController.create) routes.route(routeNames.update).put(authJwt.verifyToken, propertyController.update) routes.route(routeNames.checkProperty).get(authJwt.verifyToken, propertyController.checkProperty) routes.route(routeNames.delete).delete(authJwt.verifyToken, propertyController.deleteProperty) routes.route(routeNames.uploadImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], propertyController.uploadImage) routes.route(routeNames.deleteImage).post(authJwt.verifyToken, propertyController.deleteImage) routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, propertyController.deleteTempImage) routes.route(routeNames.getProperty).get(propertyController.getProperty) routes.route(routeNames.getProperties).post(authJwt.verifyToken, propertyController.getProperties) routes.route(routeNames.getBookingProperties).post(authJwt.verifyToken, propertyController.getBookingProperties) routes.route(routeNames.getFrontendProperties).post(propertyController.getFrontendProperties) export default routes
Tout d’abord, nous créons un routeur express. Ensuite, nous créons les routes en utilisant leur nom, leur méthode, leurs middlewares et leurs contr?leurs.
routeNames contient les noms de routes propertyRoutes?:
const routes = { create: '/api/create-property', update: '/api/update-property', delete: '/api/delete-property/:id', uploadImage: '/api/upload-property-image', deleteTempImage: '/api/delete-temp-property-image/:fileName', deleteImage: '/api/delete-property-image/:property/:image', getProperty: '/api/property/:id/:language', getProperties: '/api/properties/:page/:size', getBookingProperties: '/api/booking-properties/:page/:size', getFrontendProperties: '/api/frontend-properties/:page/:size', checkProperty: '/api/check-property/:id', } export default routes
propertyController contient la principale logique métier concernant les emplacements. Nous n'allons pas voir tout le code source du contr?leur car il est assez volumineux mais nous prendrons par exemple la fonction de création de contr?leur.
Ci-dessous le modèle de propriété?:
import { Schema, model } from 'mongoose' import * as movininTypes from ':movinin-types' import * as env from '../config/env.config' const propertySchema = new Schema<env.Property>( { name: { type: String, required: [true, "can't be blank"], }, type: { type: String, enum: [ movininTypes.PropertyType.House, movininTypes.PropertyType.Apartment, movininTypes.PropertyType.Townhouse, movininTypes.PropertyType.Plot, movininTypes.PropertyType.Farm, movininTypes.PropertyType.Commercial, movininTypes.PropertyType.Industrial, ], required: [true, "can't be blank"], }, agency: { type: Schema.Types.ObjectId, required: [true, "can't be blank"], ref: 'User', index: true, }, description: { type: String, required: [true, "can't be blank"], }, available: { type: Boolean, default: true, }, image: { type: String, }, images: { type: [String], }, bedrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, bathrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, kitchens: { type: Number, default: 1, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, parkingSpaces: { type: Number, default: 0, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, size: { type: Number, }, petsAllowed: { type: Boolean, required: [true, "can't be blank"], }, furnished: { type: Boolean, required: [true, "can't be blank"], }, minimumAge: { type: Number, required: [true, "can't be blank"], min: env.MINIMUM_AGE, max: 99, }, location: { type: Schema.Types.ObjectId, ref: 'Location', required: [true, "can't be blank"], }, address: { type: String, }, price: { type: Number, required: [true, "can't be blank"], }, hidden: { type: Boolean, default: false, }, cancellation: { type: Number, default: 0, }, aircon: { type: Boolean, default: false, }, rentalTerm: { type: String, enum: [ movininTypes.RentalTerm.Monthly, movininTypes.RentalTerm.Weekly, movininTypes.RentalTerm.Daily, movininTypes.RentalTerm.Yearly, ], required: [true, "can't be blank"], }, }, { timestamps: true, strict: true, collection: 'Property', }, ) const Property = model<env.Property>('Property', propertySchema) export default Property
Ci-dessous le type de propriété?:
export interface Property extends Document { name: string type: movininTypes.PropertyType agency: Types.ObjectId description: string image: string images?: string[] bedrooms: number bathrooms: number kitchens?: number parkingSpaces?: number, size?: number petsAllowed: boolean furnished: boolean minimumAge: number location: Types.ObjectId address?: string price: number hidden?: boolean cancellation?: number aircon?: boolean available?: boolean rentalTerm: movininTypes.RentalTerm }
Un bien est composé de :
- Un nom
- Un type (Appartement, Commercial, Ferme, Maison, Industriel, Terrain, Maison de ville)
- Une référence à l'agence qui l'a créé
- Une description
- Une image principale
- Images supplémentaires
- Nombre de chambres
- Nombre de salles de bain
- Nombre de cuisines
- Nombre de places de stationnement
- Une taille
- ?ge minimum pour la location
- Un emplacement
- Une adresse (facultatif)
- Un prix
- Une durée de location (mensuelle, hebdomadaire, journalière, annuelle)
- Prix d'annulation (réglez-le à 0 pour être inclus gratuitement, laissez-le vide si vous ne souhaitez pas l'inclure, ou fixez le prix d'annulation)
- Un drapeau qui indique si les animaux sont autorisés ou non
- Un drapeau qui indique si le bien est meublé ou non
- Un drapeau qui indique si la propriété est masquée ou non
- Un drapeau qui indique si la climatisation est disponible ou non
- Un drapeau qui indique si le bien est disponible à la location ou non
Ci-dessous se trouve la fonction de création de contr?leur?:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
L'extrémité avant
Le frontend est une application Web construite avec Node.js, React, MUI et TypeScript. Depuis le frontend, le client peut rechercher les voitures disponibles en fonction des points de prise en charge et de dép?t et de l'heure, choisir une voiture et procéder au paiement?:
- Le dossier ./frontend/src/assets/ contient du CSS et des images.
- Le dossier ./frontend/src/pages/ contient des pages React.
- Le dossier ./frontend/src/components/ contient des composants React.
- ./frontend/src/services/ contient les services client API.
- ./frontend/src/App.tsx est la principale application React qui contient des routes.
- ./frontend/src/index.tsx est le point d'entrée principal du frontend.
Les définitions de types TypeScript sont définies dans le package ./packages/movinin-types.
App.tsx est la principale application de réaction?:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Nous utilisons le chargement différé de React pour charger chaque itinéraire.
Nous n'allons pas couvrir chaque page du frontend, mais vous pouvez parcourir le code source et voir chacune d'elles.
Application mobile
La plateforme propose une application mobile native pour Android et iOS. L'application mobile est construite avec React Native, Expo et TypeScript. Comme pour le frontend, l'application mobile permet au client de rechercher des voitures disponibles en fonction des points de prise en charge et de dép?t et de l'heure, de choisir une voiture et de procéder au paiement.
Le client re?oit des notifications push si sa réservation est mise à jour depuis le backend. Les notifications push sont créées avec Node.js, Expo Server SDK et Firebase.
- Le dossier ./mobile/assets/ contient des images.
- Le dossier ./mobile/screens/ contient les principaux écrans React Native.
- Le dossier ./mobile/components/ contient des composants React Native.
- ./mobile/services/ contient des services client API.
- ./mobile/App.tsx est la principale application native de React.
Les définitions de types TypeScript sont définies dans?:
- ./mobile/types/index.d.ts
- ./mobile/types/env.d.ts
- ./mobile/miscellaneous/movininTypes.ts
./mobile/types/ est chargé dans ./mobile/tsconfig.json comme suit?:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
App.tsx est le point d'entrée principal de l'application React Native?:
importer 'react-native-gesture-handler' importer React, { useCallback, useEffect, useRef, useState } depuis 'react' importer { RootSiblingParent } depuis 'react-native-root-siblings' importer { NavigationContainer, NavigationContainerRef } depuis '@react-navigation/native' importer { StatusBar comme ExpoStatusBar } depuis 'expo-status-bar' importer { SafeAreaProvider } depuis 'react-native-safe-area-context' importer { Fournisseur } depuis 'react-native-paper' importer * en tant que SplashScreen depuis 'expo-splash-screen' importer * en tant que notifications de 'expo-notifications' importer { StripeProvider } depuis '@stripe/stripe-react-native' importer DrawerNavigator depuis './components/DrawerNavigator' importer * comme assistant depuis './common/helper' importer * en tant que NotificationService depuis './services/NotificationService' importer * en tant que UserService depuis './services/UserService' importer { GlobalProvider } depuis './context/GlobalContext' importer * en tant qu'environnement depuis './config/env.config' Notifications.setNotificationHandler({ handleNotification?: async () => ({ ShouldShowAlert?: vrai, ShouldPlaySound?: vrai, ShouldSetBadge?: vrai, }), }) // // Empêche l'écran de démarrage natif de se masquer automatiquement avant la déclaration du composant App // SplashScreen.preventAutoHideAsync() .then((result) => console.log(`SplashScreen.preventAutoHideAsync() a réussi?: ${result}`)) .catch(console.warn) // il est bon de détecter et d'inspecter explicitement toute erreur const App = () => { const [appIsReady, setAppIsReady] = useState (false) const réponseListener = useRef<Notifications.Subscription>() const navigationRef = useRef<NavigationContainerRef<StackParams>>(null) useEffect(() => { const registre = async () => { constloggedIn = attendre UserService.loggedIn() si (connecté) { const currentUser = attendre UserService.getCurrentUser() si (utilisateuractuel?._id) { attendre helper.registerPushToken (currentUser._id) } autre { aide.erreur() } } } // // Enregistre le jeton de notifications push // registre() // // Cet écouteur est déclenché chaque fois qu'un utilisateur appuie ou interagit avec une notification (fonctionne lorsque l'application est au premier plan, en arrière-plan ou supprimée) // réponseListener.current = Notifications.addNotificationResponseReceivedListener (async (réponse) => { essayer { si (navigationRef.current) { const {données} = réponse.notification.request.content si (data.booking) { si (data.user && data.notification) { attendre NotificationService.markAsRead (data.user, [data.notification]) } navigationRef.current.navigate('Réservation', { id : data.booking }) } autre { navigationRef.current.navigate('Notifications', {}) } } } attraper (erreur) { helper.error(erreur, faux) } }) return () => { Notifications.removeNotificationSubscription(responseListener.current!) } }, []) setTimeout(() => { setAppIsReady (vrai) }, 500) const onReady = useCallback(async () => { si (appIsReady) { // // Cela indique à l'écran de démarrage de se cacher immédiatement?! Si nous appelons cela après // `setAppIsReady`, alors nous pouvons voir un écran vide pendant que l'application est // chargement de son état initial et rendu de ses premiers pixels. Alors à la place, // nous masquons l'écran de démarrage une fois que nous savons que la vue racine est déjà présente // effectué la mise en page. // attendre SplashScreen.hideAsync() } }, [appIsReady]) si (!appIsReady) { renvoyer nul } retour ( <GlobalProvider> <SafeAreaProvider> <Fournisseur> <StripeProvider publiableKey={env.STRIPE_PUBLISHABLE_KEY} MerchantIdentifier={env.STRIPE_MERCHANT_IDENTIFIER}> <RootSiblingParent> <NavigationContainer ref={navigationRef} onReady={onReady}> <ExpoStatusBar> <p>Nous n'allons pas couvrir chaque écran de l'application mobile, mais vous pouvez parcourir le code source et voir chacun.</p> <h2> Tableau de bord d'administration </h2> <p>Le tableau de bord d'administration est une application Web construite avec Node.js, React, MUI et TypeScript. Depuis le backend, les administrateurs peuvent créer et gérer des fournisseurs, des voitures, des emplacements, des clients et des réservations. Lorsque de nouveaux fournisseurs sont créés depuis le backend, ils recevront un e-mail les invitant à créer un compte afin d'accéder au tableau de bord d'administration et de gérer leur flotte de voitures et leurs réservations.</p>
- Le dossier ./backend/assets/ contient du CSS et des images.
- ./backend/pages/ le dossier contient des pages React.
- Le dossier ./backend/components/ contient des composants React.
- ./backend/services/ contient les services client API.
- ./backend/App.tsx est la principale application React qui contient des routes.
- ./backend/index.tsx est le point d'entrée principal du tableau de bord d'administration.
Les définitions de types TypeScript sont définies dans le package ./packages/movinin-types.
App.tsx du tableau de bord d'administration suit une logique similaire à celle App.tsx du frontend.
Nous n'allons pas couvrir chaque page du tableau de bord d'administration mais vous pouvez parcourir le code source et voir chacune d'elles.
Points d'intérêt
Créer l'application mobile avec React Native et Expo est très simple. Expo rend le développement mobile avec React Native très simple.
Utiliser le même langage (TypeScript) pour le développement backend, frontend et mobile est très pratique.
TypeScript est un langage très intéressant et présente de nombreux avantages. En ajoutant le typage statique à JavaScript, nous pouvons éviter de nombreux bugs et produire un code de haute qualité, évolutif, plus lisible et maintenable, facile à déboguer et à tester.
C'est ?a ! J'espère que vous avez apprécié la lecture de cet article.
Ressources
- Aper?u
- Architecture
- Installation (auto-hébergé)
- Installation (VPS)
-
Installation (Docker)
- Image Docker
- SSL
- Configuration de Stripe
- Créer une application mobile
-
Base de données de démonstration
- Windows, Linux et macOS
- Docker
- Exécuter à partir de la source
-
Exécuter l'application mobile
- Prérequis
- Instructions
- Notifications push
- Changer de devise
- Ajouter une nouvelle langue
- Tests unitaires et couverture
- Journaux
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undress AI Tool
Images de déshabillage gratuites

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
échangez les visages dans n'importe quelle vidéo sans effort grace à notre outil d'échange de visage AI entièrement gratuit?!

Article chaud

Outils chauds

Bloc-notes++7.3.1
éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Il existe trois fa?ons courantes d'initier des demandes HTTP dans Node.js: utilisez des modules intégrés, Axios et Node-Fetch. 1. Utilisez le module HTTP / HTTPS intégré sans dépendances, ce qui convient aux scénarios de base, mais nécessite un traitement manuel de la couture des données et de la surveillance des erreurs, tels que l'utilisation de https.get () pour obtenir des données ou envoyer des demandes de post via .write (); 2.AXIOS est une bibliothèque tierce basée sur la promesse. Il a une syntaxe concise et des fonctions puissantes, prend en charge l'async / attendre, la conversion JSON automatique, l'intercepteur, etc. Il est recommandé de simplifier les opérations de demande asynchrones; 3.Node-Fetch fournit un style similaire à la récupération du navigateur, basé sur la promesse et la syntaxe simple

Les types de données JavaScript sont divisés en types primitifs et types de référence. Les types primitifs incluent la cha?ne, le nombre, le booléen, le nul, un non défini et le symbole. Les valeurs sont immuables et les copies sont copiées lors de l'attribution des valeurs, de sorte qu'elles ne se affectent pas; Les types de référence tels que les objets, les tableaux et les fonctions stockent les adresses de mémoire, et les variables pointant vers le même objet s'afferchent mutuellement. Le typeof et l'instance de OFF peuvent être utilisés pour déterminer les types, mais prêtent attention aux problèmes historiques de typeofnull. Comprendre ces deux types de différences peut aider à écrire un code plus stable et fiable.

Bonjour, développeurs JavaScript! Bienvenue dans JavaScript News de cette semaine! Cette semaine, nous nous concentrerons sur: le différend de marque d'Oracle avec Deno, les nouveaux objets Time JavaScript sont pris en charge par les navigateurs, les mises à jour Google Chrome et certains outils de développeurs puissants. Commen?ons! Le différend de marque d'Oracle avec la tentative de Deno Oracle d'enregistrer une marque "JavaScript" a provoqué la controverse. Ryan Dahl, le créateur de Node.js et Deno, a déposé une pétition pour annuler la marque, et il pense que JavaScript est un niveau ouvert et ne devrait pas être utilisé par Oracle

La promesse est le mécanisme central pour gérer les opérations asynchrones en JavaScript. Comprendre les appels de cha?ne, la gestion des erreurs et les combinants est la clé pour ma?triser leurs applications. 1. L'appel de la cha?ne renvoie une nouvelle promesse à travers. Puis () pour réaliser la concaténation des processus asynchrones. Chaque .then () re?oit le résultat précédent et peut renvoyer une valeur ou une promesse; 2. La gestion des erreurs doit utiliser .catch () pour attraper des exceptions pour éviter les défaillances silencieuses, et peut renvoyer la valeur par défaut dans Catch pour continuer le processus; 3. Combinateurs tels que promesse.all () (réussi avec succès uniquement après tout succès), promesse.race () (le premier achèvement est retourné) et promesse.allsetTled () (en attente de toutes les achèvements)

Cacheapi est un outil fourni par le navigateur pour mettre en cache les demandes de réseau, qui est souvent utilisée en conjonction avec travailleur de service pour améliorer les performances du site Web et l'expérience hors ligne. 1. Il permet aux développeurs de stocker manuellement des ressources telles que des scripts, des feuilles de style, des photos, etc.; 2. Il peut faire correspondre les réponses du cache en fonction des demandes; 3. Il prend en charge la suppression des caches spécifiques ou la nettoyage du cache entier; 4. Il peut mettre en ?uvre des stratégies de priorité de cache ou de priorité de réseau grace à l'écoute des événements Fetch; 5. Il est souvent utilisé pour le support hors ligne, accélérez la vitesse d'accès répétée, préchargement des ressources clés et du contenu de mise à jour des antécédents; 6. Lorsque vous l'utilisez, vous devez faire attention au contr?le de la version du cache, aux restrictions de stockage et à la différence entre le mécanisme de mise en cache HTTP.

La boucle d'événement de JavaScript gère les opérations asynchrones en coordonnant les piles d'appels, les webapis et les files d'attente de taches. 1. La pile d'appels exécute du code synchrone, et lors de la rencontre de taches asynchrones, il est remis à WebAPI pour le traitement; 2. Une fois que le WebAPI a terminé la tache en arrière-plan, il met le rappel dans la file d'attente correspondante (macro tache ou micro tache); 3. La boucle d'événement vérifie si la pile d'appels est vide. S'il est vide, le rappel est retiré de la file d'attente et poussé dans la pile d'appels pour l'exécution; 4. Micro taches (comme Promise. puis) ??prendre la priorité sur les taches macro (telles que Settimeout); 5. Comprendre la boucle d'événements permet d'éviter de bloquer le thread principal et d'optimiser l'ordre d'exécution du code.

Les bulles d'événements se propagent de l'élément cible vers l'extérieur vers le n?ud d'ancêtre, tandis que la capture d'événements se propage de la couche externe vers l'intérieur vers l'élément cible. 1. événements Bubbles: Après avoir cliqué sur l'élément enfant, l'événement déclenche l'auditeur de l'élément parent vers le haut. Par exemple, après avoir cliqué sur le bouton, il sortira d'abord cliqué sur l'enfant, puis parent. 2. Capture d'événement: définissez le troisième paramètre sur true, afin que l'auditeur soit exécuté dans l'étape de capture, tels que le déclenchement de l'écouteur de capture de l'élément parent avant de cliquer sur le bouton. 3. Les utilisations pratiques incluent la gestion unifiée des événements d'éléments enfants, le prétraitement d'interception et l'optimisation des performances. 4. Le flux d'événements DOM est divisé en trois étapes: capture, cible et bulle, et l'écouteur par défaut est exécuté dans l'étape de la bulle.

Dans les tableaux JavaScript, en plus de la carte et du filtre, il existe d'autres méthodes puissantes et rarement utilisées. 1. La réduction peut non seulement résumer, mais également compter, se regrouper, aplatir les tableaux et construire de nouvelles structures; 2. Find et FindIndex sont utilisés pour trouver des éléments ou des index individuels; 3.Il et tout sont utilisés pour déterminer si les conditions existent ou que toutes les personnes se rencontrent; 4.Sort peut être trié mais changera le tableau d'origine; 5. Faites attention à la copie du tableau lorsque vous l'utilisez pour éviter les effets secondaires. Ces méthodes rendent le code plus concis et efficace.
