Maison >interface Web >js tutoriel >De zéro à vitrine : mon parcours pour créer un site Web et une application mobile de location de voitures

De zéro à vitrine : mon parcours pour créer un site Web et une application mobile de location de voitures

Mary-Kate Olsen
Mary-Kate Olsenoriginal
2024-11-26 00:26:12701parcourir

Contenu

  1. Présentation
  2. Pile technologique
  3. Aperçu rapide
  4. API
  5. Frontend
  6. Application mobile
  7. Tableau de bord d'administration
  8. Points d'intérêt
  9. Ressources

Code source : https://github.com/aelassas/bookcars

Démo : https://bookcars.dynv6.net:3002

Introduction

L'idée est née d'un désir de construire sans frontières : un site Web et une application mobile de location de voitures entièrement personnalisables 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
  • Réagir natif
  • 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.

React se démarque comme un excellent choix grâce à :

  1. Architecture basée sur les composants
    • Vous permet de diviser les interfaces utilisateur complexes en éléments plus petits et réutilisables
    • Rend le code plus maintenable et plus facile à tester
    • Permet une meilleure organisation et réutilisation du code
  2. Performances du DOM virtuel
    • Le DOM virtuel de React met à jour efficacement uniquement ce qui est nécessaire
    • Résultat en un chargement de page plus rapide et une meilleure expérience utilisateur
    • Réduit les rendus inutiles
  3. Riche écosystème
    • Vaste bibliothèque de composants prédéfinis
    • Outillage complet
    • Grande communauté de soutien et de ressources
  4. Forte expérience de développeur
    • Rechargement à chaud pour un retour immédiat
    • Excellents outils de débogage
    • JSX rend l'écriture du code de l'interface utilisateur plus intuitive
  5. Soutien à l'industrie
    • Soutenu par Meta (anciennement Facebook)
    • Utilisé par de nombreuses grandes entreprises
    • Développement et améliorations continus
  6. Flexibilité
    • Fonctionne bien pour les petites et grandes applications
    • Peut être intégré progressivement dans des projets existants
    • Prend en charge plusieurs stratégies de rendu (côté client, côté serveur, statique)

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, l'utilisateur peut rechercher des voitures disponibles, choisir une voiture et payer.

Ci-dessous se trouve la page principale du frontend où l'utilisateur peut choisir les points et l'heure de prise en charge et de dépôt, et rechercher les voitures disponibles.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous le résultat de recherche de la page principale où l'utilisateur peut choisir une voiture à louer.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page de paiement où l'utilisateur peut définir les options de location et le paiement. Si l'utilisateur n'est pas enregistré, 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.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

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é.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page d'inscription.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Ci-dessous se trouve la page où l'utilisateur peut voir et gérer ses réservations.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Ci-dessous se trouve la page où l'utilisateur peut voir une réservation en détail.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Ci-dessous la page de contact.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page des emplacements de location de voitures.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Ci-dessous se trouve la page où le client peut voir et gérer ses notifications.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Il existe d'autres pages mais ce sont les pages principales du frontend.

Tableau de bord d'administration

Il existe trois types d'utilisateurs :

  • Admin : Il a un accès complet au tableau de bord d'administration. Il peut tout faire.
  • Fournisseur : Il a un accès restreint au tableau de bord d'administration. Il ne peut gérer que ses voitures et ses réservations.
  • Utilisateur : Il a uniquement accès au frontend et à l'application mobile. Il ne peut pas accéder au tableau de bord d'administration.

La plateforme est conçue pour fonctionner avec plusieurs fournisseurs. Chaque fournisseur peut gérer sa flotte de voitures et ses réservations depuis le tableau de bord d'administration. La plateforme peut également fonctionner avec un seul fournisseur.

À partir du tableau de bord d'administration, l'utilisateur administrateur peut créer et gérer des fournisseurs, des voitures, des emplacements, des utilisateurs et des réservations.

Lorsque l'utilisateur administrateur crée un nouveau fournisseur, le fournisseur recevra un e-mail automatique pour créer son compte afin d'accéder au tableau de bord d'administration afin qu'il puisse gérer sa flotte de voitures et ses réservations.

Vous trouverez ci-dessous la page de connexion du tableau de bord d'administration.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page du tableau de bord d'administration où les administrateurs et les fournisseurs peuvent voir et gérer les réservations.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Ci-dessous se trouve la page où le parc automobile est affiché et peut être géré.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page sur laquelle les administrateurs et les fournisseurs peuvent créer de nouvelles voitures en fournissant une image et des informations sur la voiture. Pour que les options de voiture soient incluses gratuitement, définissez 0 pour l’option de voiture correspondante. Sinon, fixez le prix de l'option ou laissez-le vide si vous ne souhaitez pas l'inclure.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page où les administrateurs et les fournisseurs peuvent modifier les voitures.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page où les administrateurs peuvent gérer les utilisateurs de la plateforme.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Vous trouverez ci-dessous la page où modifier les réservations.

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

Il existe d'autres pages mais ce sont les pages principales du tableau de bord d'administration.

API

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

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, comme les fonctions liées à la gestion des voitures, des réservations et des clients, et d'autres ne nécessitent pas d'authentification, comme la récupération des emplacements et des voitures 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 * as env from './config/env.config'
import * as databaseHelper from './common/databaseHelper'
import app from './app'
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 supplierRoutes from './routes/supplierRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import carRoutes from './routes/carRoutes'
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('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

await helper.mkdir(env.CDN_USERS)
await helper.mkdir(env.CDN_TEMP_USERS)
await helper.mkdir(env.CDN_CARS)
await helper.mkdir(env.CDN_TEMP_CARS)
await helper.mkdir(env.CDN_LOCATIONS)
await helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app

Tout d'abord, on récupère la chaîne de connexion MongoDB, puis on établit 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, carRoutes 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
  • supplierRoutes : fournit des fonctions REST liées aux fournisseurs
  • countryRoutes : fournit des fonctions REST liées aux pays
  • locationRoutes : fournit des fonctions REST liées aux emplacements
  • carRoutes : fournit des fonctions REST liées aux voitures
  • 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, countryRoutes et voyons comment cela a été réalisé :

import express from 'express'
import routeNames from '../config/countryRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as countryController from '../controllers/countryController'

const routes = express.Router()

routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate)
routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update)
routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry)
routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry)
routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries)
routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations)
routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry)
routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId)

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 countryRoutes :

const routes = {
  validate: '/api/validate-country',
  create: '/api/create-country',
  update: '/api/update-country/:id',
  delete: '/api/delete-country/:id',
  getCountry: '/api/country/:id/:language',
  getCountries: '/api/countries/:page/:size/:language',
  getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations',
  checkCountry: '/api/check-country/:id',
  getCountryId: '/api/country-id/:name/:language',
}

export default routes

countryController contient la principale logique métier concernant les pays. 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 du pays :

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const countrySchema = new Schema<env.Country>(
  {
    values: {
      type: [Schema.Types.ObjectId],
      ref: 'LocationValue',
      required: [true, "can't be blank"],
      validate: (value: any): boolean => Array.isArray(value),
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Country',
  },
)

const Country = model<env.Country>('Country', countrySchema)

export default Country

Vous trouverez ci-dessous le type env.Country TypeScript :

export interface Country extends Document {
  values: Types.ObjectId[]
  name?: string
}

Un pays a plusieurs valeurs. Un par langue. Par défaut, les langues anglaise et française sont prises en charge.

Vous trouverez ci-dessous le modèle LocationValue :

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 * as env from './config/env.config'
import * as databaseHelper from './common/databaseHelper'
import app from './app'
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))
}

Ci-dessous se trouve le type env.LocationValue TypeScript :

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 supplierRoutes from './routes/supplierRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import carRoutes from './routes/carRoutes'
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('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

await helper.mkdir(env.CDN_USERS)
await helper.mkdir(env.CDN_TEMP_USERS)
await helper.mkdir(env.CDN_CARS)
await helper.mkdir(env.CDN_TEMP_CARS)
await helper.mkdir(env.CDN_LOCATIONS)
await helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app

Une LocationValue a un code de langue (ISO 639-1) et une valeur de chaîne.

Ci-dessous se trouve la fonction de création de contrôleur :

import express from 'express'
import routeNames from '../config/countryRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as countryController from '../controllers/countryController'

const routes = express.Router()

routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate)
routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update)
routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry)
routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry)
routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries)
routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations)
routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry)
routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId)

export default routes

Dans cette fonction, on récupère le corps de la requête, on parcourt les valeurs fournies dans le corps (une valeur par langue) et on crée une LocationValue. Enfin, nous créons le pays en fonction des valeurs de localisation créées.

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/bookcars-types.

App.tsx est la principale application de réaction :

const routes = {
  validate: '/api/validate-country',
  create: '/api/create-country',
  update: '/api/update-country/:id',
  delete: '/api/delete-country/:id',
  getCountry: '/api/country/:id/:language',
  getCountries: '/api/countries/:page/:size/:language',
  getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations',
  checkCountry: '/api/check-country/:id',
  getCountryId: '/api/country-id/:name/:language',
}

export default routes

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 ouvrir le code source et voir chacune d'elles si vous le souhaitez.

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/bookcarsTypes.ts

./mobile/types/ est chargé dans ./mobile/tsconfig.json comme suit :

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const countrySchema = new Schema<env.Country>(
  {
    values: {
      type: [Schema.Types.ObjectId],
      ref: 'LocationValue',
      required: [true, "can't be blank"],
      validate: (value: any): boolean => Array.isArray(value),
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Country',
  },
)

const Country = model<env.Country>('Country', countrySchema)

export default Country

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 ouvrir le code source et voir chacun si vous le souhaitez.</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 email les invitant à créer un compte afin d'accéder au backend 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/bookcars-types.

App.tsx du backend 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 ouvrir le code source et voir chacune si vous le souhaitez.

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

  1. Aperçu
  2. Architecture
  3. Installation (auto-hébergé)
  4. Installation (VPS)
  5. Installation (Docker)
    1. Image Docker
    2. SSL
  6. Configuration de Stripe
  7. Créer une application mobile
  8. Base de données de démonstration
    1. Windows, Linux et macOS
    2. Docker
  9. Exécuter à partir de la source
  10. Exécuter l'application mobile
    1. Prérequis
    2. Instructions
    3. Notifications push
  11. Changer de devise
  12. Ajouter une nouvelle langue
  13. Tests unitaires et couverture
  14. 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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn