DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Develop Full Stack Flight Booking System

Creating a full-stack flight booking system with Next.js for the front end and NestJS for the back end is a substantial project. Here are some key features and functionalities that such a system could include:

User Features

  1. User Registration and Authentication:

    • Sign up, login, and logout.
    • Password recovery/reset.
    • Social media login (Google, Facebook, etc.).
  2. User Profile:

    • View and edit profile information.
    • Upload profile picture.
  3. Flight Search and Booking:

    • Search flights by destination, date, and other criteria.
    • View flight details (airline, departure/arrival time, duration, etc.).
    • Filter and sort search results.
    • Book selected flights.
    • View and manage bookings.
    • Receive booking confirmations via email.
  4. Payment Integration:

    • Integration with payment gateways (PayPal, Stripe, etc.).
    • Secure payment processing.
    • View payment history.
  5. Notifications:

    • Email and SMS notifications for booking confirmations, flight status updates, etc.
  6. Reviews and Ratings:

    • Rate and review airlines and flights.
    • View reviews and ratings from other users.

Admin Features

  1. Dashboard:

    • Overview of bookings, users, flights, and revenue.
  2. Flight Management:

    • Add, edit, and delete flights.
    • Manage flight schedules and availability.
  3. User Management:

    • View and manage users.
    • Assign roles (e.g., admin, customer support).
  4. Booking Management:

    • View and manage all bookings.
    • Cancel or modify bookings.
  5. Reporting and Analytics:

    • Generate reports on bookings, revenue, user activity, etc.
    • Analyze data to make informed decisions.

Functionalities

  1. API Integration:

    • Integrate with third-party APIs for real-time flight data (e.g., Amadeus, Skyscanner).
  2. Security:

    • Implement authentication and authorization (JWT, OAuth).
    • Secure data transmission (HTTPS).
  3. Performance Optimization:

    • Optimize database queries.
    • Implement caching mechanisms.
    • Use CDN for static assets.
  4. Scalability:

    • Design for horizontal scalability.
    • Use microservices architecture if needed.
  5. Testing:

    • Unit and integration tests for back-end (NestJS).
    • End-to-end tests for the front end (Next.js).
  6. Documentation:

    • API documentation (Swagger).
    • User manuals and help sections.

Optional Advanced Features

  1. Multi-language Support:

    • Provide interface and notifications in multiple languages.
  2. Loyalty Program:

    • Implement a points or rewards system for frequent flyers.
  3. Customer Support:

    • Live chat or chatbot integration.
    • Ticketing system for support requests.
  4. Mobile App:

    • Develop a companion mobile app (using React Native or Flutter).
  5. Dynamic Pricing:

    • Implement algorithms for dynamic pricing based on demand, season, etc.

By incorporating these features and functionalities, you'll have a comprehensive flight booking system that caters to both users and administrators effectively.

To implement user registration and authentication features in a NestJS backend, you'll need to set up a few key components, including modules, controllers, services, and guards for handling authentication and authorization. Below is a step-by-step guide with sample code for implementing these features:

1. Setting Up the Project

First, create a new NestJS project if you haven't already:

nest new flight-booking-backend
cd flight-booking-backend
Enter fullscreen mode Exit fullscreen mode

2. Install Required Packages

You'll need some additional packages for authentication:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs
Enter fullscreen mode Exit fullscreen mode

3. Create Auth Module

Generate the authentication module and related components:

nest generate module auth
nest generate service auth
nest generate controller auth
Enter fullscreen mode Exit fullscreen mode

4. User Entity

Create a user entity to represent users in the database. Assuming you are using TypeORM, it would look something like this:

// src/user/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  password: string;

  @Column({ nullable: true })
  googleId: string;

  @Column({ nullable: true })
  facebookId: string;
}
Enter fullscreen mode Exit fullscreen mode

5. Auth Service

Implement the authentication logic in the AuthService:

// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcryptjs';
import { User } from '../user/user.entity';
import { JwtPayload } from './jwt-payload.interface';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    private jwtService: JwtService,
  ) {}

  async signUp(authCredentialsDto: AuthCredentialsDto): Promise<void> {
    const { email, password } = authCredentialsDto;

    const salt = await bcrypt.genSalt();
    const hashedPassword = await bcrypt.hash(password, salt);

    const user = this.userRepository.create({ email, password: hashedPassword });
    await this.userRepository.save(user);
  }

  async validateUser(email: string, pass: string): Promise<any> {
    const user = await this.userRepository.findOne({ email });

    if (user && (await bcrypt.compare(pass, user.password))) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload: JwtPayload = { email: user.email, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Auth Controller

Create the controller to handle authentication requests:

// src/auth/auth.controller.ts
import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';
import { JwtAuthGuard } from './jwt-auth.guard';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('/signup')
  signUp(@Body() authCredentialsDto: AuthCredentialsDto): Promise<void> {
    return this.authService.signUp(authCredentialsDto);
  }

  @Post('/login')
  async login(@Body() authCredentialsDto: AuthCredentialsDto) {
    const user = await this.authService.validateUser(authCredentialsDto.email, authCredentialsDto.password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return this.authService.login(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

7. DTOs

Define the DTOs (Data Transfer Objects) for authentication:

// src/auth/dto/auth-credentials.dto.ts
export class AuthCredentialsDto {
  email: string;
  password: string;
}
Enter fullscreen mode Exit fullscreen mode

8. JWT Strategy

Configure the JWT strategy for handling authentication:

// src/auth/jwt.strategy.ts
import { Strategy, ExtractJwt } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtPayload } from './jwt-payload.interface';
import { AuthService } from './auth.service';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private authService: AuthService,
    private configService: ConfigService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_SECRET'),
    });
  }

  async validate(payload: JwtPayload) {
    const user = await this.authService.validateUser(payload.email, null);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

9. JWT Auth Guard

Create a guard to protect routes:

// src/auth/jwt-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}
Enter fullscreen mode Exit fullscreen mode

10. Auth Module Configuration

Configure the AuthModule to include necessary imports and providers:

// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../user/user.entity';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule,
    TypeOrmModule.forFeature([User]),
    PassportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: { expiresIn: '60m' },
      }),
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}
Enter fullscreen mode Exit fullscreen mode

11. Environment Configuration

Finally, add the JWT secret to your environment variables:

# .env
JWT_SECRET=your_jwt_secret_key
Enter fullscreen mode Exit fullscreen mode

And configure TypeORM to connect to your database:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';
import { User } from './user/user.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User],
      synchronize: true,
    }),
    AuthModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

This setup should provide a robust foundation for user registration, login, and JWT-based authentication. You can expand on this by adding social login functionality, password reset, and other features as needed.

To implement user profile management, including viewing and editing profile information and uploading a profile picture, you'll need to extend the functionality of your existing NestJS application. Here's how to do it step by step:

1. Update User Entity

First, update the User entity to include fields for profile information and profile picture:

// src/user/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  password: string;

  @Column({ nullable: true })
  firstName: string;

  @Column({ nullable: true })
  lastName: string;

  @Column({ nullable: true })
  profilePicture: string;

  @Column({ nullable: true })
  googleId: string;

  @Column({ nullable: true })
  facebookId: string;
}
Enter fullscreen mode Exit fullscreen mode

2. Create User Service

Implement methods in the UserService to handle profile operations:

// src/user/user.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { UpdateProfileDto } from './dto/update-profile.dto';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async getProfile(userId: number): Promise<User> {
    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`User with ID ${userId} not found`);
    }
    return user;
  }

  async updateProfile(userId: number, updateProfileDto: UpdateProfileDto): Promise<User> {
    await this.userRepository.update(userId, updateProfileDto);
    return this.getProfile(userId);
  }

  async updateProfilePicture(userId: number, profilePicture: string): Promise<User> {
    await this.userRepository.update(userId, { profilePicture });
    return this.getProfile(userId);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Create DTOs

Define the DTOs for updating profile information:

// src/user/dto/update-profile.dto.ts
export class UpdateProfileDto {
  firstName?: string;
  lastName?: string;
  profilePicture?: string;
}
Enter fullscreen mode Exit fullscreen mode

4. Create User Controller

Implement the UserController to handle profile-related requests:

// src/user/user.controller.ts
import { Controller, Get, Patch, Body, Param, UseGuards, UploadedFile, UseInterceptors } from '@nestjs/common';
import { UserService } from './user.service';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';

@Controller('user')
@UseGuards(JwtAuthGuard)
export class UserController {
  constructor(private userService: UserService) {}

  @Get(':id')
  getProfile(@Param('id') id: string) {
    return this.userService.getProfile(+id);
  }

  @Patch(':id')
  updateProfile(@Param('id') id: string, @Body() updateProfileDto: UpdateProfileDto) {
    return this.userService.updateProfile(+id, updateProfileDto);
  }

  @Patch(':id/profile-picture')
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: './uploads/profile-pictures',
        filename: (req, file, cb) => {
          const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
          const ext = extname(file.originalname);
          cb(null, `${file.fieldname}-${uniqueSuffix}${ext}`);
        },
      }),
    }),
  )
  uploadProfilePicture(@Param('id') id: string, @UploadedFile() file: Express.Multer.File) {
    const profilePicture = `/uploads/profile-pictures/${file.filename}`;
    return this.userService.updateProfilePicture(+id, profilePicture);
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Update App Module

Ensure that the UserModule is imported in your AppModule and configure the ServeStaticModule for serving uploaded files:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';
import { UserModule } from './user/user.module';
import { User } from './user/user.entity';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User],
      synchronize: true,
    }),
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '..', 'uploads'),
      serveRoot: '/uploads',
    }),
    AuthModule,
    UserModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

6. Create User Module

Finally, create the UserModule to tie everything together:

// src/user/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserService],
  controllers: [UserController],
  exports: [UserService],
})
export class UserModule {}
Enter fullscreen mode Exit fullscreen mode

With this setup, you have created endpoints for viewing and editing user profile information and uploading a profile picture. The profile pictures are saved to the ./uploads/profile-pictures directory and served statically via the /uploads URL path.

To implement flight search and booking functionality in the backend using NestJS, we'll need to define entities, services, controllers, and DTOs. Here's a step-by-step guide:

1. Define Flight and Booking Entities

First, define the Flight and Booking entities:

// src/flight/flight.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Flight {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  airline: string;

  @Column()
  from: string;

  @Column()
  to: string;

  @Column()
  departureTime: Date;

  @Column()
  arrivalTime: Date;

  @Column('decimal')
  price: number;

  @Column()
  duration: string;
}
Enter fullscreen mode Exit fullscreen mode
// src/booking/booking.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Entity()
export class Booking {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, user => user.bookings)
  @JoinColumn({ name: 'userId' })
  user: User;

  @ManyToOne(() => Flight)
  @JoinColumn({ name: 'flightId' })
  flight: Flight;

  @Column()
  bookingDate: Date;

  @Column()
  status: string;
}
Enter fullscreen mode Exit fullscreen mode

2. Create DTOs

Define DTOs for creating bookings and searching flights:

// src/flight/dto/search-flight.dto.ts
export class SearchFlightDto {
  from: string;
  to: string;
  departureDate: Date;
  returnDate?: Date;
}

// src/booking/dto/create-booking.dto.ts
export class CreateBookingDto {
  flightId: number;
  userId: number;
}
Enter fullscreen mode Exit fullscreen mode

3. Create Services

Implement services for flight search and booking operations:

// src/flight/flight.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Flight } from './flight.entity';
import { SearchFlightDto } from './dto/search-flight.dto';

@Injectable()
export class FlightService {
  constructor(
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
  ) {}

  async searchFlights(searchFlightDto: SearchFlightDto): Promise<Flight[]> {
    const { from, to, departureDate, returnDate } = searchFlightDto;
    const query = this.flightRepository.createQueryBuilder('flight')
      .where('flight.from = :from', { from })
      .andWhere('flight.to = :to', { to })
      .andWhere('DATE(flight.departureTime) = :departureDate', { departureDate });

    if (returnDate) {
      query.andWhere('DATE(flight.arrivalTime) = :returnDate', { returnDate });
    }

    return query.getMany();
  }

  async getFlightById(id: number): Promise<Flight> {
    return this.flightRepository.findOne(id);
  }
}
Enter fullscreen mode Exit fullscreen mode
// src/booking/booking.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Booking } from './booking.entity';
import { CreateBookingDto } from './dto/create-booking.dto';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Injectable()
export class BookingService {
  constructor(
    @InjectRepository(Booking)
    private bookingRepository: Repository<Booking>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
  ) {}

  async createBooking(createBookingDto: CreateBookingDto): Promise<Booking> {
    const { flightId, userId } = createBookingDto;

    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`User with ID ${userId} not found`);
    }

    const flight = await this.flightRepository.findOne(flightId);
    if (!flight) {
      throw new NotFoundException(`Flight with ID ${flightId} not found`);
    }

    const booking = this.bookingRepository.create({
      user,
      flight,
      bookingDate: new Date(),
      status: 'CONFIRMED',
    });

    await this.bookingRepository.save(booking);
    // Add email confirmation logic here

    return booking;
  }

  async getBookingsByUserId(userId: number): Promise<Booking[]> {
    return this.bookingRepository.find({
      where: { user: { id: userId } },
      relations: ['flight'],
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Create Controllers

Implement controllers to handle flight search and booking requests:

// src/flight/flight.controller.ts
import { Controller, Get, Query, Param } from '@nestjs/common';
import { FlightService } from './flight.service';
import { SearchFlightDto } from './dto/search-flight.dto';

@Controller('flights')
export class FlightController {
  constructor(private flightService: FlightService) {}

  @Get()
  searchFlights(@Query() searchFlightDto: SearchFlightDto) {
    return this.flightService.searchFlights(searchFlightDto);
  }

  @Get(':id')
  getFlightById(@Param('id') id: number) {
    return this.flightService.getFlightById(id);
  }
}
Enter fullscreen mode Exit fullscreen mode
// src/booking/booking.controller.ts
import { Controller, Post, Body, Get, Param, UseGuards } from '@nestjs/common';
import { BookingService } from './booking.service';
import { CreateBookingDto } from './dto/create-booking.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('bookings')
@UseGuards(JwtAuthGuard)
export class BookingController {
  constructor(private bookingService: BookingService) {}

  @Post()
  createBooking(@Body() createBookingDto: CreateBookingDto) {
    return this.bookingService.createBooking(createBookingDto);
  }

  @Get(':userId')
  getBookingsByUserId(@Param('userId') userId: number) {
    return this.bookingService.getBookingsByUserId(userId);
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Update App Module

Ensure that the FlightModule and BookingModule are imported in your AppModule:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { FlightModule } from './flight/flight.module';
import { BookingModule } from './booking/booking.module';
import { User } from './user/user.entity';
import { Flight } from './flight/flight.entity';
import { Booking } from './booking/booking.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User, Flight, Booking],
      synchronize: true,
    }),
    AuthModule,
    UserModule,
    FlightModule,
    BookingModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

6. Create Flight and Booking Modules

Create the Flight and Booking modules to tie everything together:

// src/flight/flight.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FlightService } from './flight.service';
import { FlightController } from './flight.controller';
import { Flight } from './flight.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Flight])],
  providers: [FlightService],
  controllers: [FlightController],
  exports: [FlightService],
})
export class FlightModule {}
Enter fullscreen mode Exit fullscreen mode
// src/booking/booking.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingService } from './booking.service';
import { BookingController } from './booking.controller';
import { Booking } from './booking.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Booking, User, Flight])],
  providers: [BookingService],
  controllers: [BookingController],
  exports: [BookingService],
})
export class BookingModule {}
Enter fullscreen mode Exit fullscreen mode

7. Email Confirmation (Optional)

For email confirmation, you can use a package like nodemailer. Here’s an example of how to integrate it:

// src/booking/booking.service.ts (inside createBooking method)
import * as nodemailer from 'nodemailer';

async function sendBookingConfirmation(email: string, booking: Booking) {
  const transporter = nodemailer.createTransport({
    host: 'smtp.example.com',
    port: 587,
    secure: false,
    auth: {
      user: 'your_email@example.com',
      pass: 'your_email_password',
    },
  });

  const mailOptions = {
    from: 'your_email@example.com',
    to: email,
    subject:

 'Booking Confirmation',
    text: `Your booking is confirmed. Booking details: ${JSON.stringify(booking)}`,
  };

  await transporter.sendMail(mailOptions);
}

// Call this function after saving the booking
await this.bookingRepository.save(booking);
await sendBookingConfirmation(user.email, booking);
Enter fullscreen mode Exit fullscreen mode

This setup should provide a comprehensive backend for flight search and booking functionalities. You can expand on this by adding more features like filtering, sorting, and better error handling as needed.

To integrate payment processing in a NestJS backend, you can use popular payment gateways like Stripe or PayPal. Below is an example of integrating Stripe for payment processing, secure payments, and viewing payment history.

1. Install Required Packages

First, install the Stripe package:

npm install @nestjs/common @nestjs/config stripe @nestjs/stripe
Enter fullscreen mode Exit fullscreen mode

2. Configure Stripe

Add your Stripe API keys to your environment variables:

# .env
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_PUBLISHABLE_KEY=your_stripe_publishable_key
Enter fullscreen mode Exit fullscreen mode

3. Create Payment Module

Generate the payment module and related components:

nest generate module payment
nest generate service payment
nest generate controller payment
Enter fullscreen mode Exit fullscreen mode

4. Create Payment Entity

Define a Payment entity to store payment information:

// src/payment/payment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from '../user/user.entity';
import { Booking } from '../booking/booking.entity';

@Entity()
export class Payment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  stripePaymentId: string;

  @Column()
  amount: number;

  @Column()
  currency: string;

  @Column()
  status: string;

  @ManyToOne(() => User)
  @JoinColumn({ name: 'userId' })
  user: User;

  @ManyToOne(() => Booking)
  @JoinColumn({ name: 'bookingId' })
  booking: Booking;

  @Column()
  createdAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

5. Create Payment Service

Implement methods in the PaymentService to handle payment processing and viewing payment history:

// src/payment/payment.service.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Payment } from './payment.entity';
import { User } from '../user/user.entity';
import { Booking } from '../booking/booking.entity';
import { ConfigService } from '@nestjs/config';
import Stripe from 'stripe';

@Injectable()
export class PaymentService {
  private stripe: Stripe;

  constructor(
    @InjectRepository(Payment)
    private paymentRepository: Repository<Payment>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Booking)
    private bookingRepository: Repository<Booking>,
    private configService: ConfigService,
  ) {
    this.stripe = new Stripe(this.configService.get<string>('STRIPE_SECRET_KEY'), {
      apiVersion: '2020-08-27',
    });
  }

  async processPayment(userId: number, bookingId: number, token: string): Promise<Payment> {
    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new BadRequestException(`User with ID ${userId} not found`);
    }

    const booking = await this.bookingRepository.findOne(bookingId);
    if (!booking) {
      throw new BadRequestException(`Booking with ID ${bookingId} not found`);
    }

    const amount = 1000; // Amount in cents
    const currency = 'usd';

    const charge = await this.stripe.charges.create({
      amount,
      currency,
      source: token,
      description: `Payment for booking ${bookingId}`,
      receipt_email: user.email,
    });

    const payment = this.paymentRepository.create({
      stripePaymentId: charge.id,
      amount,
      currency,
      status: charge.status,
      user,
      booking,
      createdAt: new Date(),
    });

    return this.paymentRepository.save(payment);
  }

  async getPaymentHistory(userId: number): Promise<Payment[]> {
    return this.paymentRepository.find({
      where: { user: { id: userId } },
      relations: ['booking'],
      order: { createdAt: 'DESC' },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Create Payment Controller

Implement the PaymentController to handle payment-related requests:

// src/payment/payment.controller.ts
import { Controller, Post, Body, Get, Param, UseGuards } from '@nestjs/common';
import { PaymentService } from './payment.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('payments')
@UseGuards(JwtAuthGuard)
export class PaymentController {
  constructor(private paymentService: PaymentService) {}

  @Post('process')
  async processPayment(@Body('userId') userId: number, @Body('bookingId') bookingId: number, @Body('token') token: string) {
    return this.paymentService.processPayment(userId, bookingId, token);
  }

  @Get('history/:userId')
  async getPaymentHistory(@Param('userId') userId: number) {
    return this.paymentService.getPaymentHistory(userId);
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Update App Module

Ensure that the PaymentModule is imported in your AppModule:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { FlightModule } from './flight/flight.module';
import { BookingModule } from './booking/booking.module';
import { PaymentModule } from './payment/payment.module';
import { User } from './user/user.entity';
import { Flight } from './flight/flight.entity';
import { Booking } from './booking/booking.entity';
import { Payment } from './payment/payment.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User, Flight, Booking, Payment],
      synchronize: true,
    }),
    AuthModule,
    UserModule,
    FlightModule,
    BookingModule,
    PaymentModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

8. Create Payment Module

Create the PaymentModule to tie everything together:

// src/payment/payment.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PaymentService } from './payment.service';
import { PaymentController } from './payment.controller';
import { Payment } from './payment.entity';
import { User } from '../user/user.entity';
import { Booking } from '../booking/booking.entity';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [TypeOrmModule.forFeature([Payment, User, Booking]), ConfigModule],
  providers: [PaymentService],
  controllers: [PaymentController],
  exports: [PaymentService],
})
export class PaymentModule {}
Enter fullscreen mode Exit fullscreen mode

9. Stripe Payment Processing Frontend Integration (Optional)

For the frontend, you would typically use Stripe.js to collect payment details and generate a payment token, which is then sent to your backend API to process the payment. Here’s a brief example of how you might collect the token:

<!-- Add this script to your HTML -->
<script src="https://js.stripe.com/v3/"></script>
Enter fullscreen mode Exit fullscreen mode
// Example frontend code to get the Stripe token
const stripe = Stripe('your_stripe_publishable_key');

const handlePayment = async () => {
  const { token } = await stripe.createToken(cardElement); // cardElement is a reference to the card input field

  // Send token to your backend
  const response = await fetch('/payments/process', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      userId: 1,
      bookingId: 1,
      token: token.id,
    }),
  });

  const result = await response.json();
  console.log(result);
};
Enter fullscreen mode Exit fullscreen mode

With this setup, you should have a fully functional payment processing system integrated with Stripe, along with secure payment handling and payment history viewing capabilities in your NestJS backend.

To implement email and SMS notifications for booking confirmations, flight status updates, and other notifications in a NestJS backend, we need to integrate with email and SMS service providers. We'll use Nodemailer for email notifications and Twilio for SMS notifications.

1. Install Required Packages

First, install the necessary packages:

npm install @nestjs/common @nestjs/config nodemailer twilio
Enter fullscreen mode Exit fullscreen mode

2. Configure Environment Variables

Add your Twilio and email SMTP service credentials to your environment variables:

# .env
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your_email@example.com
SMTP_PASS=your_email_password
TWILIO_ACCOUNT_SID=your_twilio_account_sid
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_PHONE_NUMBER=your_twilio_phone_number
Enter fullscreen mode Exit fullscreen mode

3. Create Notification Module

Generate the notification module and related components:

nest generate module notification
nest generate service notification
nest generate controller notification
Enter fullscreen mode Exit fullscreen mode

4. Create Notification Service

Implement methods in the NotificationService to handle email and SMS notifications:

// src/notification/notification.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as nodemailer from 'nodemailer';
import { Twilio } from 'twilio';

@Injectable()
export class NotificationService {
  private transporter: nodemailer.Transporter;
  private twilioClient: Twilio;

  constructor(private configService: ConfigService) {
    this.transporter = nodemailer.createTransport({
      host: this.configService.get<string>('SMTP_HOST'),
      port: this.configService.get<number>('SMTP_PORT'),
      secure: false,
      auth: {
        user: this.configService.get<string>('SMTP_USER'),
        pass: this.configService.get<string>('SMTP_PASS'),
      },
    });

    this.twilioClient = new Twilio(
      this.configService.get<string>('TWILIO_ACCOUNT_SID'),
      this.configService.get<string>('TWILIO_AUTH_TOKEN'),
    );
  }

  async sendEmail(to: string, subject: string, text: string, html?: string) {
    const mailOptions = {
      from: this.configService.get<string>('SMTP_USER'),
      to,
      subject,
      text,
      html,
    };

    await this.transporter.sendMail(mailOptions);
  }

  async sendSms(to: string, body: string) {
    await this.twilioClient.messages.create({
      body,
      from: this.configService.get<string>('TWILIO_PHONE_NUMBER'),
      to,
    });
  }

  async sendBookingConfirmationEmail(userEmail: string, bookingDetails: any) {
    const subject = 'Booking Confirmation';
    const text = `Your booking is confirmed. Booking details: ${JSON.stringify(bookingDetails)}`;
    await this.sendEmail(userEmail, subject, text);
  }

  async sendBookingConfirmationSms(userPhone: string, bookingDetails: any) {
    const body = `Your booking is confirmed. Booking details: ${JSON.stringify(bookingDetails)}`;
    await this.sendSms(userPhone, body);
  }

  // Add more methods for other types of notifications as needed
}
Enter fullscreen mode Exit fullscreen mode

5. Integrate Notification Service in Booking Service

Update the BookingService to send notifications upon booking confirmation:

// src/booking/booking.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Booking } from './booking.entity';
import { CreateBookingDto } from './dto/create-booking.dto';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';
import { NotificationService } from '../notification/notification.service';

@Injectable()
export class BookingService {
  constructor(
    @InjectRepository(Booking)
    private bookingRepository: Repository<Booking>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
    private notificationService: NotificationService,
  ) {}

  async createBooking(createBookingDto: CreateBookingDto): Promise<Booking> {
    const { flightId, userId } = createBookingDto;

    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`User with ID ${userId} not found`);
    }

    const flight = await this.flightRepository.findOne(flightId);
    if (!flight) {
      throw new NotFoundException(`Flight with ID ${flightId} not found`);
    }

    const booking = this.bookingRepository.create({
      user,
      flight,
      bookingDate: new Date(),
      status: 'CONFIRMED',
    });

    await this.bookingRepository.save(booking);

    // Send notifications
    await this.notificationService.sendBookingConfirmationEmail(user.email, booking);
    if (user.phone) {
      await this.notificationService.sendBookingConfirmationSms(user.phone, booking);
    }

    return booking;
  }

  async getBookingsByUserId(userId: number): Promise<Booking[]> {
    return this.bookingRepository.find({
      where: { user: { id: userId } },
      relations: ['flight'],
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Update App Module

Ensure that the NotificationModule is imported in your AppModule:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { FlightModule } from './flight/flight.module';
import { BookingModule } from './booking/booking.module';
import { PaymentModule } from './payment/payment.module';
import { NotificationModule } from './notification/notification.module';
import { User } from './user/user.entity';
import { Flight } from './flight/flight.entity';
import { Booking } from './booking/booking.entity';
import { Payment } from './payment/payment.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User, Flight, Booking, Payment],
      synchronize: true,
    }),
    AuthModule,
    UserModule,
    FlightModule,
    BookingModule,
    PaymentModule,
    NotificationModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

7. Create Notification Module

Create the NotificationModule to tie everything together:

// src/notification/notification.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { NotificationService } from './notification.service';

@Module({
  imports: [ConfigModule],
  providers: [NotificationService],
  exports: [NotificationService],
})
export class NotificationModule {}
Enter fullscreen mode Exit fullscreen mode

This setup should provide a robust notification system that sends email and SMS notifications for booking confirmations and other updates. You can expand this by adding more notification types and integrating with additional email/SMS providers if needed.

To implement a reviews and ratings feature for airlines and flights in your NestJS backend, we need to create entities for reviews and ratings, services to handle the business logic, and controllers to expose the endpoints. Here’s how you can do it:

1. Create Review and Rating Entities

Define the Review and Rating entities. These will store the reviews and ratings given by users to flights.

// src/review/review.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Entity()
export class Review {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  content: string;

  @CreateDateColumn()
  createdAt: Date;

  @ManyToOne(() => User)
  @JoinColumn({ name: 'userId' })
  user: User;

  @ManyToOne(() => Flight)
  @JoinColumn({ name: 'flightId' })
  flight: Flight;
}

// src/rating/rating.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Entity()
export class Rating {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  score: number;

  @CreateDateColumn()
  createdAt: Date;

  @ManyToOne(() => User)
  @JoinColumn({ name: 'userId' })
  user: User;

  @ManyToOne(() => Flight)
  @JoinColumn({ name: 'flightId' })
  flight: Flight;
}
Enter fullscreen mode Exit fullscreen mode

2. Create Review and Rating Modules

Generate the review and rating modules and services:

nest generate module review
nest generate service review
nest generate controller review

nest generate module rating
nest generate service rating
nest generate controller rating
Enter fullscreen mode Exit fullscreen mode

3. Create Review Service

Implement methods in the ReviewService to handle adding and viewing reviews:

// src/review/review.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Review } from './review.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';
import { CreateReviewDto } from './dto/create-review.dto';

@Injectable()
export class ReviewService {
  constructor(
    @InjectRepository(Review)
    private reviewRepository: Repository<Review>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
  ) {}

  async addReview(createReviewDto: CreateReviewDto): Promise<Review> {
    const { userId, flightId, content } = createReviewDto;

    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`User with ID ${userId} not found`);
    }

    const flight = await this.flightRepository.findOne(flightId);
    if (!flight) {
      throw new NotFoundException(`Flight with ID ${flightId} not found`);
    }

    const review = this.reviewRepository.create({
      content,
      user,
      flight,
      createdAt: new Date(),
    });

    return this.reviewRepository.save(review);
  }

  async getReviewsByFlight(flightId: number): Promise<Review[]> {
    return this.reviewRepository.find({
      where: { flight: { id: flightId } },
      relations: ['user'],
      order: { createdAt: 'DESC' },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Create Rating Service

Implement methods in the RatingService to handle adding and viewing ratings:

// src/rating/rating.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Rating } from './rating.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';
import { CreateRatingDto } from './dto/create-rating.dto';

@Injectable()
export class RatingService {
  constructor(
    @InjectRepository(Rating)
    private ratingRepository: Repository<Rating>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
  ) {}

  async addRating(createRatingDto: CreateRatingDto): Promise<Rating> {
    const { userId, flightId, score } = createRatingDto;

    const user = await this.userRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`User with ID ${userId} not found`);
    }

    const flight = await this.flightRepository.findOne(flightId);
    if (!flight) {
      throw new NotFoundException(`Flight with ID ${flightId} not found`);
    }

    const rating = this.ratingRepository.create({
      score,
      user,
      flight,
      createdAt: new Date(),
    });

    return this.ratingRepository.save(rating);
  }

  async getRatingsByFlight(flightId: number): Promise<Rating[]> {
    return this.ratingRepository.find({
      where: { flight: { id: flightId } },
      relations: ['user'],
      order: { createdAt: 'DESC' },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Create Review and Rating DTOs

Define DTOs for creating reviews and ratings:

// src/review/dto/create-review.dto.ts
export class CreateReviewDto {
  userId: number;
  flightId: number;
  content: string;
}

// src/rating/dto/create-rating.dto.ts
export class CreateRatingDto {
  userId: number;
  flightId: number;
  score: number;
}
Enter fullscreen mode Exit fullscreen mode

6. Create Review and Rating Controllers

Implement the ReviewController and RatingController to expose the endpoints:

// src/review/review.controller.ts
import { Controller, Post, Body, Get, Param, UseGuards } from '@nestjs/common';
import { ReviewService } from './review.service';
import { CreateReviewDto } from './dto/create-review.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('reviews')
@UseGuards(JwtAuthGuard)
export class ReviewController {
  constructor(private reviewService: ReviewService) {}

  @Post()
  async addReview(@Body() createReviewDto: CreateReviewDto) {
    return this.reviewService.addReview(createReviewDto);
  }

  @Get('flight/:flightId')
  async getReviewsByFlight(@Param('flightId') flightId: number) {
    return this.reviewService.getReviewsByFlight(flightId);
  }
}

// src/rating/rating.controller.ts
import { Controller, Post, Body, Get, Param, UseGuards } from '@nestjs/common';
import { RatingService } from './rating.service';
import { CreateRatingDto } from './dto/create-rating.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('ratings')
@UseGuards(JwtAuthGuard)
export class RatingController {
  constructor(private ratingService: RatingService) {}

  @Post()
  async addRating(@Body() createRatingDto: CreateRatingDto) {
    return this.ratingService.addRating(createRatingDto);
  }

  @Get('flight/:flightId')
  async getRatingsByFlight(@Param('flightId') flightId: number) {
    return this.ratingService.getRatingsByFlight(flightId);
  }
}
Enter fullscreen mode Exit fullscreen mode

7. Update App Module

Ensure that the ReviewModule and RatingModule are imported in your AppModule:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { FlightModule } from './flight/flight.module';
import { BookingModule } from './booking/booking.module';
import { PaymentModule } from './payment/payment.module';
import { NotificationModule } from './notification/notification.module';
import { ReviewModule } from './review/review.module';
import { RatingModule } from './rating/rating.module';
import { User } from './user/user.entity';
import { Flight } from './flight/flight.entity';
import { Booking } from './booking/booking.entity';
import { Payment } from './payment/payment.entity';
import { Review } from './review/review.entity';
import { Rating } from './rating/rating.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      entities: [User, Flight, Booking, Payment, Review, Rating],
      synchronize: true,
    }),
    AuthModule,
    UserModule,
    FlightModule,
    BookingModule,
    PaymentModule,
    NotificationModule,
    ReviewModule,
    RatingModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

8. Create Review and Rating Modules

Create the ReviewModule and RatingModule to tie everything together:

// src/review

/review.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ReviewService } from './review.service';
import { ReviewController } from './review.controller';
import { Review } from './review.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Review, User, Flight])],
  providers: [ReviewService],
  controllers: [ReviewController],
})
export class ReviewModule {}

// src/rating/rating.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RatingService } from './rating.service';
import { RatingController } from './rating.controller';
import { Rating } from './rating.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Rating, User, Flight])],
  providers: [RatingService],
  controllers: [RatingController],
})
export class RatingModule {}
Enter fullscreen mode Exit fullscreen mode

This setup should provide a comprehensive review and rating system for flights. You can expand this by adding more fields, validation, and additional features as needed.

To implement admin features such as a dashboard, flight management, and user management in your NestJS backend, you need to create the necessary entities, services, controllers, and guards for authorization. Here’s how you can do it:

1. Install Required Packages

Make sure you have the necessary packages installed:

npm install @nestjs/common @nestjs/config @nestjs/typeorm @nestjs/jwt bcryptjs
Enter fullscreen mode Exit fullscreen mode

2. Create Admin Guard

Create a guard to protect routes that only admins should access:

// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector, private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];

    if (!token) {
      return false;
    }

    const user = this.jwtService.decode(token);
    return requiredRoles.some((role) => user['roles']?.includes(role));
  }
}
Enter fullscreen mode Exit fullscreen mode

Add a decorator to set roles on routes:

// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
Enter fullscreen mode Exit fullscreen mode

3. Create Dashboard Service

Implement methods in the DashboardService to fetch booking, user, flight, and revenue data:

// src/admin/dashboard.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Booking } from '../booking/booking.entity';
import { User } from '../user/user.entity';
import { Flight } from '../flight/flight.entity';
import { Payment } from '../payment/payment.entity';

@Injectable()
export class DashboardService {
  constructor(
    @InjectRepository(Booking)
    private bookingRepository: Repository<Booking>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
    @InjectRepository(Payment)
    private paymentRepository: Repository<Payment>,
  ) {}

  async getOverview() {
    const totalBookings = await this.bookingRepository.count();
    const totalUsers = await this.userRepository.count();
    const totalFlights = await this.flightRepository.count();
    const totalRevenue = await this.paymentRepository
      .createQueryBuilder('payment')
      .select('SUM(payment.amount)', 'sum')
      .getRawOne();

    return {
      totalBookings,
      totalUsers,
      totalFlights,
      totalRevenue: totalRevenue.sum || 0,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Create Flight Management Service

Implement methods in the FlightService to handle adding, editing, and deleting flights:

// src/flight/flight.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Flight } from './flight.entity';
import { CreateFlightDto } from './dto/create-flight.dto';
import { UpdateFlightDto } from './dto/update-flight.dto';

@Injectable()
export class FlightService {
  constructor(
    @InjectRepository(Flight)
    private flightRepository: Repository<Flight>,
  ) {}

  async createFlight(createFlightDto: CreateFlightDto): Promise<Flight> {
    const flight = this.flightRepository.create(createFlightDto);
    return this.flightRepository.save(flight);
  }

  async updateFlight(id: number, updateFlightDto: UpdateFlightDto): Promise<Flight> {
    const flight = await this.flightRepository.preload({
      id,
      ...updateFlightDto,
    });

    if (!flight) {
      throw new NotFoundException(`Flight with ID ${id} not found`);
    }

    return this.flightRepository.save(flight);
  }

  async deleteFlight(id: number): Promise<void> {
    const result = await this.flightRepository.delete(id);
    if (result.affected === 0) {
      throw new NotFoundException(`Flight with ID ${id} not found`);
    }
  }

  async getFlights(): Promise<Flight[]> {
    return this.flightRepository.find();
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Create User Management Service

Implement methods in the UserService to handle viewing and managing users, and assigning roles:

// src/user/user.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async getUsers(): Promise<User[]> {
    return this.userRepository.find();
  }

  async updateUser(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.userRepository.preload({
      id,
      ...updateUserDto,
    });

    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }

    return this.userRepository.save(user);
  }

  async deleteUser(id: number): Promise<void> {
    const result = await this.userRepository.delete(id);
    if (result.affected === 0) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Create DTOs

Define DTOs for creating and updating flights and users:

// src/flight/dto/create-flight.dto.ts
export class CreateFlightDto {
  airline: string;
  departure: string;
  arrival: string;
  departureTime: Date;
  arrivalTime: Date;
  price: number;
  availability: number;
}

// src/flight/dto/update-flight.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateFlightDto } from './create-flight.dto';

export class UpdateFlightDto extends PartialType(CreateFlightDto) {}

// src/user/dto/update-user.dto.ts
export class UpdateUserDto {
  username?: string;
  email?: string;
  roles?: string[];
}
Enter fullscreen mode Exit fullscreen mode

7. Create Admin Controllers

Implement the AdminController to expose the endpoints for the dashboard, flight management, and user management:

// src/admin/admin.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common';
import { DashboardService } from './dashboard.service';
import { FlightService } from '../flight/flight.service';
import { UserService } from '../user/user.service';
import { CreateFlightDto } from '../flight/dto/create-flight.dto';
import { UpdateFlightDto } from '../flight/dto/update-flight.dto';
import { UpdateUserDto } from '../user/dto/update-user.dto';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';

@Controller('admin')
@UseGuards(RolesGuard)
@Roles('admin')
export class AdminController {
  constructor(
    private dashboardService: DashboardService,
    private flightService: FlightService,
    private userService: UserService,
  ) {}

  @Get('dashboard')
  async getDashboard() {
    return this.dashboardService.getOverview();
  }

  @Get('flights')
  async getFlights() {
    return this.flightService.getFlights();
  }

  @Post('flights')
  async createFlight(@Body() createFlightDto: CreateFlightDto) {
    return this.flightService.createFlight(createFlightDto);
  }

  @Put('flights/:id')
  async updateFlight(@Param('id') id: number, @Body() updateFlightDto: UpdateFlightDto) {
    return this.flightService.updateFlight(id, updateFlightDto);
  }

  @Delete('flights/:id')
  async deleteFlight(@Param('id') id: number) {
    return this.flightService.deleteFlight(id);
  }

  @Get('users')
  async getUsers() {
    return this.userService.getUsers();
  }

  @Put('users/:id')
  async updateUser(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.updateUser(id, updateUserDto);
  }

  @Delete('users/:id')
  async deleteUser(@Param('id') id: number) {
    return this.userService.deleteUser(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

8. Update App Module

Ensure that the AdminModule, FlightModule, and UserModule are imported in your AppModule:

Creating a backend for a full-stack flight booking system using NestJS involves several components. Below is a step-by-step guide with code snippets for each feature you listed. This guide assumes you have a basic understanding of NestJS and TypeScript.

  1. Initialize NestJS Project

First, create a new NestJS project:

   nest new flight-booking-backend
   cd flight-booking-backend
Enter fullscreen mode Exit fullscreen mode
  1. Install Required Packages

Install the necessary packages:

   npm install @nestjs/typeorm typeorm mysql @nestjs/passport passport passport-local passport-jwt bcryptjs
   npm install @nestjs/jwt
Enter fullscreen mode Exit fullscreen mode
  1. Set Up Database Connection

Configure TypeORM in your app.module.ts:

   import { Module } from '@nestjs/common';
   import { TypeOrmModule } from '@nestjs/typeorm';
   import { User } from './users/user.entity';
   import { Flight } from './flights/flight.entity';
   import { Booking } from './bookings/booking.entity';

   @Module({
     imports: [
       TypeOrmModule.forRoot({
         type: 'mysql',
         host: 'localhost',
         port: 3306,
         username: 'root',
         password: 'password',
         database: 'flight_booking',
         entities: [User, Flight, Booking],
         synchronize: true,
       }),
       TypeOrmModule.forFeature([User, Flight, Booking]),
     ],
   })
   export class AppModule {}
Enter fullscreen mode Exit fullscreen mode
  1. User Registration and Authentication

Create a user entity and authentication module.

user.entity.ts

   import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

   @Entity()
   export class User {
     @PrimaryGeneratedColumn()
     id: number;

     @Column()
     username: string;

     @Column()
     email: string;

     @Column()
     password: string;

     @Column({ default: 'user' })
     role: string;
   }
Enter fullscreen mode Exit fullscreen mode

auth.module.ts

   import { Module } from '@nestjs/common';
   import { JwtModule } from '@nestjs/jwt';
   import { PassportModule } from '@nestjs/passport';
   import { AuthService } from './auth.service';
   import { JwtStrategy } from './jwt.strategy';
   import { UsersModule } from '../users/users.module';

   @Module({
     imports: [
       PassportModule,
       JwtModule.register({
         secret: 'secretKey',
         signOptions: { expiresIn: '1h' },
       }),
       UsersModule,
     ],
     providers: [AuthService, JwtStrategy],
     exports: [AuthService],
   })
   export class AuthModule {}
Enter fullscreen mode Exit fullscreen mode

auth.service.ts

   import { Injectable } from '@nestjs/common';
   import { JwtService } from '@nestjs/jwt';
   import * as bcrypt from 'bcryptjs';
   import { UsersService } from '../users/users.service';

   @Injectable()
   export class AuthService {
     constructor(
       private readonly usersService: UsersService,
       private readonly jwtService: JwtService,
     ) {}

     async validateUser(username: string, password: string): Promise<any> {
       const user = await this.usersService.findOne(username);
       if (user && bcrypt.compareSync(password, user.password)) {
         const { password, ...result } = user;
         return result;
       }
       return null;
     }

     async login(user: any) {
       const payload = { username: user.username, sub: user.id, role: user.role };
       return {
         access_token: this.jwtService.sign(payload),
       };
     }

     async register(user: any) {
       const hashedPassword = bcrypt.hashSync(user.password, 8);
       return this.usersService.create({ ...user, password: hashedPassword });
     }
   }
Enter fullscreen mode Exit fullscreen mode

jwt.strategy.ts

   import { Injectable } from '@nestjs/common';
   import { PassportStrategy } from '@nestjs/passport';
   import { ExtractJwt, Strategy } from 'passport-jwt';

   @Injectable()
   export class JwtStrategy extends PassportStrategy(Strategy) {
     constructor() {
       super({
         jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
         ignoreExpiration: false,
         secretOrKey: 'secretKey',
       });
     }

     async validate(payload: any) {
       return { userId: payload.sub, username: payload.username, role: payload.role };
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Flight Search and Booking

Create flight and booking entities and their respective modules and services.

flight.entity.ts

   import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

   @Entity()
   export class Flight {
     @PrimaryGeneratedColumn()
     id: number;

     @Column()
     airline: string;

     @Column()
     departureTime: Date;

     @Column()
     arrivalTime: Date;

     @Column()
     from: string;

     @Column()
     to: string;

     @Column()
     price: number;

     @Column()
     seatsAvailable: number;
   }
Enter fullscreen mode Exit fullscreen mode

flight.module.ts

   import { Module } from '@nestjs/common';
   import { TypeOrmModule } from '@nestjs/typeorm';
   import { Flight } from './flight.entity';
   import { FlightService } from './flight.service';
   import { FlightController } from './flight.controller';

   @Module({
     imports: [TypeOrmModule.forFeature([Flight])],
     providers: [FlightService],
     controllers: [FlightController],
   })
   export class FlightModule {}
Enter fullscreen mode Exit fullscreen mode

flight.service.ts

   import { Injectable } from '@nestjs/common';
   import { InjectRepository } from '@nestjs/typeorm';
   import { Repository } from 'typeorm';
   import { Flight } from './flight.entity';

   @Injectable()
   export class FlightService {
     constructor(
       @InjectRepository(Flight)
       private readonly flightRepository: Repository<Flight>,
     ) {}

     findAll(): Promise<Flight[]> {
       return this.flightRepository.find();
     }

     findOne(id: number): Promise<Flight> {
       return this.flightRepository.findOne(id);
     }

     create(flight: Flight): Promise<Flight> {
       return this.flightRepository.save(flight);
     }

     update(id: number, flight: Flight): Promise<any> {
       return this.flightRepository.update(id, flight);
     }

     delete(id: number): Promise<any> {
       return this.flightRepository.delete(id);
     }
   }
Enter fullscreen mode Exit fullscreen mode

flight.controller.ts

   import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
   import { FlightService } from './flight.service';
   import { Flight } from './flight.entity';

   @Controller('flights')
   export class FlightController {
     constructor(private readonly flightService: FlightService) {}

     @Get()
     findAll(): Promise<Flight[]> {
       return this.flightService.findAll();
     }

     @Get(':id')
     findOne(@Param('id') id: number): Promise<Flight> {
       return this.flightService.findOne(id);
     }

     @Post()
     create(@Body() flight: Flight): Promise<Flight> {
       return this.flightService.create(flight);
     }

     @Put(':id')
     update(@Param('id') id: number, @Body() flight: Flight): Promise<any> {
       return this.flightService.update(id, flight);
     }

     @Delete(':id')
     delete(@Param('id') id: number): Promise<any> {
       return this.flightService.delete(id);
     }
   }
Enter fullscreen mode Exit fullscreen mode

booking.entity.ts

   import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
   import { User } from '../users/user.entity';
   import { Flight } from '../flights/flight.entity';

   @Entity()
   export class Booking {
     @PrimaryGeneratedColumn()
     id: number;

     @ManyToOne(() => User)
     user: User;

     @ManyToOne(() => Flight)
     flight: Flight;

     @Column()
     status: string;

     @Column()
     bookingDate: Date;
   }
Enter fullscreen mode Exit fullscreen mode

booking.module.ts

   import { Module } from '@nestjs/common';
   import { TypeOrmModule } from '@nestjs/typeorm';
   import { Booking } from './booking.entity';
   import { BookingService } from './booking.service';
   import { BookingController } from './booking.controller';

   @Module({
     imports: [TypeOrmModule.forFeature([Booking])],
     providers: [BookingService],
     controllers: [BookingController],
   })
   export class BookingModule {}
Enter fullscreen mode Exit fullscreen mode

booking.service.ts

   import { Injectable } from '@nestjs/common';
   import { InjectRepository } from '@nestjs/typeorm';
   import { Repository } from 'typeorm';
   import { Booking } from './booking.entity';

   @Injectable()
   export class BookingService {
     constructor(
       @InjectRepository(Booking)
       private readonly bookingRepository: Repository<Booking>,
     ) {}

     findAll(): Promise<Booking[]> {
       return this.bookingRepository.find({ relations: ['user', 'flight'] });
     }

     findOne(id: number): Promise<Booking> {
       return this.bookingRepository.findOne(id, { relations: ['user', 'flight'] });
     }

     create(booking: Booking): Promise<Booking> {
       return this.bookingRepository.save(booking);
     }

     update

(id: number, booking: Booking): Promise<any> {
       return this.bookingRepository.update(id, booking);
     }

     delete(id: number): Promise<any> {
       return this.bookingRepository.delete(id);
     }
   }
Enter fullscreen mode Exit fullscreen mode

booking.controller.ts

   import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
   import { BookingService } from './booking.service';
   import { Booking } from './booking.entity';

   @Controller('bookings')
   export class BookingController {
     constructor(private readonly bookingService: BookingService) {}

     @Get()
     findAll(): Promise<Booking[]> {
       return this.bookingService.findAll();
     }

     @Get(':id')
     findOne(@Param('id') id: number): Promise<Booking> {
       return this.bookingService.findOne(id);
     }

     @Post()
     create(@Body() booking: Booking): Promise<Booking> {
       return this.bookingService.create(booking);
     }

     @Put(':id')
     update(@Param('id') id: number, @Body() booking: Booking): Promise<any> {
       return this.bookingService.update(id, booking);
     }

     @Delete(':id')
     delete(@Param('id') id: number): Promise<any> {
       return this.bookingService.delete(id);
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Admin Features

For admin features, you can create specific controllers and services that will be accessible only to users with admin roles. You can use the @Roles decorator to restrict access.

roles.guard.ts

   import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
   import { Reflector } from '@nestjs/core';

   @Injectable()
   export class RolesGuard implements CanActivate {
     constructor(private reflector: Reflector) {}

     canActivate(context: ExecutionContext): boolean {
       const roles = this.reflector.get<string[]>('roles', context.getHandler());
       if (!roles) {
         return true;
       }
       const request = context.switchToHttp().getRequest();
       const user = request.user;
       return roles.some(role => role === user.role);
     }
   }
Enter fullscreen mode Exit fullscreen mode

roles.decorator.ts

   import { SetMetadata } from '@nestjs/common';

   export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
Enter fullscreen mode Exit fullscreen mode

flight.controller.ts (Admin)

   import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common';
   import { FlightService } from './flight.service';
   import { Flight } from './flight.entity';
   import { Roles } from '../auth/roles.decorator';
   import { RolesGuard } from '../auth/roles.guard';
   import { AuthGuard } from '@nestjs/passport';

   @Controller('admin/flights')
   @UseGuards(AuthGuard('jwt'), RolesGuard)
   @Roles('admin')
   export class AdminFlightController {
     constructor(private readonly flightService: FlightService) {}

     @Get()
     findAll(): Promise<Flight[]> {
       return this.flightService.findAll();
     }

     @Get(':id')
     findOne(@Param('id') id: number): Promise<Flight> {
       return this.flightService.findOne(id);
     }

     @Post()
     create(@Body() flight: Flight): Promise<Flight> {
       return this.flightService.create(flight);
     }

     @Put(':id')
     update(@Param('id') id: number, @Body() flight: Flight): Promise<any> {
       return this.flightService.update(id, flight);
     }

     @Delete(':id')
     delete(@Param('id') id: number): Promise<any> {
       return this.flightService.delete(id);
     }
   }
Enter fullscreen mode Exit fullscreen mode

This is a comprehensive setup for the backend of a flight booking system. You can expand and refine it according to your specific requirements, such as adding more sophisticated search and filter options, integrating third-party APIs for real-time flight data, or implementing payment gateways.

Creating the frontend for a user registration and authentication system using Next.js involves several components. Below is a step-by-step guide with code snippets for each feature you listed. This guide assumes you have a basic understanding of Next.js, React, and Apollo Client.

  1. Initialize Next.js Project

First, create a new Next.js project:

   npx create-next-app@latest flight-booking-frontend
   cd flight-booking-frontend
Enter fullscreen mode Exit fullscreen mode
  1. Install Required Packages

Install the necessary packages:

   npm install @apollo/client graphql next-auth @mantine/core @mantine/hooks @mantine/dropzone
Enter fullscreen mode Exit fullscreen mode
  1. Set Up Apollo Client

Create an ApolloClient instance in a file called apollo-client.js:

apollo-client.js

   import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
   import { setContext } from '@apollo/client/link/context';

   const httpLink = createHttpLink({
     uri: 'http://localhost:4000/graphql',
   });

   const authLink = setContext((_, { headers }) => {
     const token = localStorage.getItem('token');
     return {
       headers: {
         ...headers,
         authorization: token ? `Bearer ${token}` : '',
       },
     };
   });

   const client = new ApolloClient({
     link: authLink.concat(httpLink),
     cache: new InMemoryCache(),
   });

   export default client;
Enter fullscreen mode Exit fullscreen mode
  1. User Registration and Authentication

Create the necessary GraphQL queries and mutations:

graphql/queries.js

   import { gql } from '@apollo/client';

   export const LOGIN_USER = gql`
     mutation LoginUser($username: String!, $password: String!) {
       login(username: $username, password: $password) {
         access_token
       }
     }
   `;

   export const REGISTER_USER = gql`
     mutation RegisterUser($username: String!, $email: String!, $password: String!) {
       register(username: $username, email: $email, password: $password) {
         id
         username
         email
       }
     }
   `;
Enter fullscreen mode Exit fullscreen mode

Create a login page:

pages/login.js

   import { useState } from 'react';
   import { useMutation } from '@apollo/client';
   import { LOGIN_USER } from '../graphql/queries';
   import { useRouter } from 'next/router';

   export default function Login() {
     const [username, setUsername] = useState('');
     const [password, setPassword] = useState('');
     const [login, { data, loading, error }] = useMutation(LOGIN_USER);
     const router = useRouter();

     const handleSubmit = async (e) => {
       e.preventDefault();
       try {
         const { data } = await login({ variables: { username, password } });
         localStorage.setItem('token', data.login.access_token);
         router.push('/');
       } catch (error) {
         console.error('Login failed', error);
       }
     };

     return (
       <div>
         <h1>Login</h1>
         <form onSubmit={handleSubmit}>
           <div>
             <label>Username</label>
             <input
               type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)}
             />
           </div>
           <div>
             <label>Password</label>
             <input
               type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)}
             />
           </div>
           <button type="submit" disabled={loading}>
             {loading ? 'Logging in...' : 'Login'}
           </button>
           {error && <p>Error: {error.message}</p>}
         </form>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode

Create a registration page:

pages/register.js

   import { useState } from 'react';
   import { useMutation } from '@apollo/client';
   import { REGISTER_USER } from '../graphql/queries';
   import { useRouter } from 'next/router';

   export default function Register() {
     const [username, setUsername] = useState('');
     const [email, setEmail] = useState('');
     const [password, setPassword] = useState('');
     const [register, { data, loading, error }] = useMutation(REGISTER_USER);
     const router = useRouter();

     const handleSubmit = async (e) => {
       e.preventDefault();
       try {
         await register({ variables: { username, email, password } });
         router.push('/login');
       } catch (error) {
         console.error('Registration failed', error);
       }
     };

     return (
       <div>
         <h1>Register</h1>
         <form onSubmit={handleSubmit}>
           <div>
             <label>Username</label>
             <input
               type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)}
             />
           </div>
           <div>
             <label>Email</label>
             <input
               type="email"
               value={email}
               onChange={(e) => setEmail(e.target.value)}
             />
           </div>
           <div>
             <label>Password</label>
             <input
               type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)}
             />
           </div>
           <button type="submit" disabled={loading}>
             {loading ? 'Registering...' : 'Register'}
           </button>
           {error && <p>Error: {error.message}</p>}
         </form>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode
  1. User Profile

Create a profile page to view and edit profile information:

pages/profile.js

   import { useQuery, useMutation } from '@apollo/client';
   import { useState } from 'react';
   import { GET_USER_PROFILE, UPDATE_USER_PROFILE } from '../graphql/queries';
   import { useRouter } from 'next/router';

   export default function Profile() {
     const { data, loading, error } = useQuery(GET_USER_PROFILE);
     const [updateProfile] = useMutation(UPDATE_USER_PROFILE);
     const [username, setUsername] = useState('');
     const [email, setEmail] = useState('');
     const router = useRouter();

     if (loading) return <p>Loading...</p>;
     if (error) return <p>Error: {error.message}</p>;

     const handleSubmit = async (e) => {
       e.preventDefault();
       try {
         await updateProfile({ variables: { username, email } });
         router.push('/');
       } catch (error) {
         console.error('Update failed', error);
       }
     };

     return (
       <div>
         <h1>Profile</h1>
         <form onSubmit={handleSubmit}>
           <div>
             <label>Username</label>
             <input
               type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)}
             />
           </div>
           <div>
             <label>Email</label>
             <input
               type="email"
               value={email}
               onChange={(e) => setEmail(e.target.value)}
             />
           </div>
           <button type="submit">Update</button>
         </form>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode

graphql/queries.js

   import { gql } from '@apollo/client';

   export const GET_USER_PROFILE = gql`
     query GetUserProfile {
       userProfile {
         id
         username
         email
       }
     }
   `;

   export const UPDATE_USER_PROFILE = gql`
     mutation UpdateUserProfile($username: String!, $email: String!) {
       updateUserProfile(username: $username, email: $email) {
         id
         username
         email
       }
     }
   `;
Enter fullscreen mode Exit fullscreen mode
  1. Upload Profile Picture

Use Mantine for the file upload component. Install Mantine and set up the file upload:

   npm install @mantine/core @mantine/dropzone
Enter fullscreen mode Exit fullscreen mode

Create a file upload component:

components/ProfilePictureUpload.js

   import { useState } from 'react';
   import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone';
   import { useMutation } from '@apollo/client';
   import { UPLOAD_PROFILE_PICTURE } from '../graphql/queries';

   export default function ProfilePictureUpload() {
     const [files, setFiles] = useState([]);
     const [uploadProfilePicture] = useMutation(UPLOAD_PROFILE_PICTURE);

     const handleDrop = async (acceptedFiles) => {
       setFiles(acceptedFiles);
       const formData = new FormData();
       formData.append('file', acceptedFiles[0]);

       try {
         await uploadProfilePicture({ variables: { file: acceptedFiles[0] } });
       } catch (error) {
         console.error('Upload failed', error);
       }
     };

     return (
       <Dropzone
         onDrop={handleDrop}
         accept={IMAGE_MIME_TYPE}
         multiple={false}
       >
         {(status) => (
           <div>
             <p>Drag images here or click to select files</p>
           </div>
         )}
       </Dropzone>
     );
   }
Enter fullscreen mode Exit fullscreen mode

graphql/queries.js

   import

 { gql } from '@apollo/client';

   export const UPLOAD_PROFILE_PICTURE = gql`
     mutation UploadProfilePicture($file: Upload!) {
       uploadProfilePicture(file: $file) {
         url
       }
     }
   `;
Enter fullscreen mode Exit fullscreen mode
  1. Integrate with Pages

Add the ProfilePictureUpload component to your profile page:

pages/profile.js

   import ProfilePictureUpload from '../components/ProfilePictureUpload';

   export default function Profile() {
     // ...existing code

     return (
       <div>
         <h1>Profile</h1>
         <ProfilePictureUpload />
         <form onSubmit={handleSubmit}>
           {/* ...existing code */}
         </form>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode
  1. Social Media Login

Set up social media login using next-auth:

   npm install next-auth
Enter fullscreen mode Exit fullscreen mode

Configure next-auth in a file called [...nextauth].js in the pages/api/auth directory:

pages/api/auth/[...nextauth].js

   import NextAuth from 'next-auth';
   import Providers from 'next-auth/providers';

   export default NextAuth({
     providers: [
       Providers.Google({
         clientId: process.env.GOOGLE_CLIENT_ID,
         clientSecret: process.env.GOOGLE_CLIENT_SECRET,
       }),
       Providers.Facebook({
         clientId: process.env.FACEBOOK_CLIENT_ID,
         clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
       }),
     ],
     callbacks: {
       async jwt(token, user) {
         if (user) {
           token.id = user.id;
         }
         return token;
       },
       async session(session, token) {
         session.user.id = token.id;
         return session;
       },
     },
   });
Enter fullscreen mode Exit fullscreen mode

Create environment variables for your social media client IDs and secrets:

.env.local

   GOOGLE_CLIENT_ID=your-google-client-id
   GOOGLE_CLIENT_SECRET=your-google-client-secret
   FACEBOOK_CLIENT_ID=your-facebook-client-id
   FACEBOOK_CLIENT_SECRET=your-facebook-client-secret
Enter fullscreen mode Exit fullscreen mode

Add social media login buttons to your login page:

pages/login.js

   import { signIn } from 'next-auth/client';

   export default function Login() {
     // ...existing code

     return (
       <div>
         <h1>Login</h1>
         <form onSubmit={handleSubmit}>
           {/* ...existing code */}
         </form>
         <button onClick={() => signIn('google')}>Login with Google</button>
         <button onClick={() => signIn('facebook')}>Login with Facebook</button>
       </div>
     );
   }
Enter fullscreen mode Exit fullscreen mode

This should cover the main features for user registration, authentication, profile management, and social media login for your flight booking system's frontend using Next.js.

To implement flight search, booking, and management features on the frontend using Next.js, you need to set up several components and pages. Below is a step-by-step guide to help you build these features.

1. Initialize Next.js Project and Install Dependencies

If you haven't already, create a Next.js project and install the necessary dependencies:

npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
npm install @apollo/client graphql react-hook-form date-fns
Enter fullscreen mode Exit fullscreen mode

2. Set Up Apollo Client

Create an ApolloClient instance to interact with your GraphQL backend.

apollo-client.js

import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'http://localhost:4000/graphql',
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

3. GraphQL Queries and Mutations

Define the necessary queries and mutations for flight search, booking, and user bookings.

graphql/queries.js

import { gql } from '@apollo/client';

export const SEARCH_FLIGHTS = gql`
  query SearchFlights($from: String!, $to: String!, $departureDate: String!) {
    searchFlights(from: $from, to: $to, departureDate: $departureDate) {
      id
      airline
      from
      to
      departureTime
      arrivalTime
      duration
      price
    }
  }
`;

export const BOOK_FLIGHT = gql`
  mutation BookFlight($flightId: ID!, $userId: ID!) {
    bookFlight(flightId: $flightId, userId: $userId) {
      id
      flight {
        id
        airline
        from
        to
        departureTime
        arrivalTime
        duration
      }
      user {
        id
        username
      }
      bookingTime
    }
  }
`;

export const GET_USER_BOOKINGS = gql`
  query GetUserBookings($userId: ID!) {
    userBookings(userId: $userId) {
      id
      flight {
        id
        airline
        from
        to
        departureTime
        arrivalTime
        duration
      }
      bookingTime
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

4. Flight Search Component

Create a flight search component to search for flights by destination, date, and other criteria.

components/FlightSearch.js

import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useLazyQuery } from '@apollo/client';
import { SEARCH_FLIGHTS } from '../graphql/queries';
import { format } from 'date-fns';

export default function FlightSearch({ onFlightsFound }) {
  const { register, handleSubmit } = useForm();
  const [searchFlights, { data, loading, error }] = useLazyQuery(SEARCH_FLIGHTS);

  const onSubmit = async (formData) => {
    const { from, to, departureDate } = formData;
    await searchFlights({
      variables: {
        from,
        to,
        departureDate: format(new Date(departureDate), 'yyyy-MM-dd'),
      },
    });

    if (data) {
      onFlightsFound(data.searchFlights);
    }
  };

  return (
    <div>
      <h2>Search Flights</h2>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label>From:</label>
          <input type="text" {...register('from')} required />
        </div>
        <div>
          <label>To:</label>
          <input type="text" {...register('to')} required />
        </div>
        <div>
          <label>Departure Date:</label>
          <input type="date" {...register('departureDate')} required />
        </div>
        <button type="submit" disabled={loading}>
          {loading ? 'Searching...' : 'Search'}
        </button>
        {error && <p>Error: {error.message}</p>}
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Flight List Component

Create a component to display the list of searched flights and allow users to book a flight.

components/FlightList.js

import { useMutation } from '@apollo/client';
import { BOOK_FLIGHT } from '../graphql/queries';

export default function FlightList({ flights, userId }) {
  const [bookFlight] = useMutation(BOOK_FLIGHT);

  const handleBook = async (flightId) => {
    try {
      await bookFlight({ variables: { flightId, userId } });
      alert('Flight booked successfully!');
    } catch (error) {
      console.error('Booking failed', error);
    }
  };

  return (
    <div>
      <h2>Available Flights</h2>
      {flights.map((flight) => (
        <div key={flight.id}>
          <p>Airline: {flight.airline}</p>
          <p>From: {flight.from}</p>
          <p>To: {flight.to}</p>
          <p>Departure: {flight.departureTime}</p>
          <p>Arrival: {flight.arrivalTime}</p>
          <p>Duration: {flight.duration}</p>
          <p>Price: ${flight.price}</p>
          <button onClick={() => handleBook(flight.id)}>Book Flight</button>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Bookings Page

Create a page to view and manage user bookings.

pages/bookings.js

import { useQuery } from '@apollo/client';
import { GET_USER_BOOKINGS } from '../graphql/queries';
import { useEffect, useState } from 'react';

export default function Bookings({ userId }) {
  const { data, loading, error } = useQuery(GET_USER_BOOKINGS, {
    variables: { userId },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Your Bookings</h2>
      {data.userBookings.map((booking) => (
        <div key={booking.id}>
          <p>Airline: {booking.flight.airline}</p>
          <p>From: {booking.flight.from}</p>
          <p>To: {booking.flight.to}</p>
          <p>Departure: {booking.flight.departureTime}</p>
          <p>Arrival: {booking.flight.arrivalTime}</p>
          <p>Duration: {booking.flight.duration}</p>
          <p>Booking Time: {booking.bookingTime}</p>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

7. Home Page

Integrate the flight search and flight list components into the home page.

pages/index.js

import { useState } from 'react';
import FlightSearch from '../components/FlightSearch';
import FlightList from '../components/FlightList';
import { useRouter } from 'next/router';

export default function Home() {
  const [flights, setFlights] = useState([]);
  const router = useRouter();
  const userId = 'current-user-id'; // Replace with the actual logged-in user ID

  const handleFlightsFound = (foundFlights) => {
    setFlights(foundFlights);
  };

  return (
    <div>
      <h1>Flight Booking System</h1>
      <FlightSearch onFlightsFound={handleFlightsFound} />
      {flights.length > 0 && <FlightList flights={flights} userId={userId} />}
      <button onClick={() => router.push('/bookings')}>View Bookings</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

8. Setup Email Notifications (Optional)

To handle email notifications for booking confirmations, you would typically implement this feature on the backend. You can trigger an email notification when a booking is created using a service like SendGrid, Nodemailer, or any other email service.

Final Steps

Ensure that your backend GraphQL API supports all the necessary queries and mutations for flights and bookings. This setup provides a robust foundation for your flight booking system's frontend. You can further enhance it with features like filtering, sorting, and more advanced error handling as needed.

Integrating payment gateways like Stripe into your Next.js application involves setting up payment forms, handling secure payment processing, and displaying payment history. Below is a step-by-step guide to implement these features on the frontend.

1. Initialize Project and Install Dependencies

If you haven't already, create a Next.js project and install the necessary dependencies:

npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
npm install @stripe/stripe-js @stripe/react-stripe-js @apollo/client graphql react-hook-form date-fns
Enter fullscreen mode Exit fullscreen mode

2. Set Up Apollo Client

If you haven't already, set up Apollo Client as shown in the previous sections.

3. Create Payment Form Component

Create a payment form component using Stripe's React components.

components/CheckoutForm.js

import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
import { useState } from 'react';
import { useMutation } from '@apollo/client';
import { CREATE_PAYMENT_INTENT, SAVE_PAYMENT } from '../graphql/queries';

export default function CheckoutForm({ bookingId }) {
  const stripe = useStripe();
  const elements = useElements();
  const [createPaymentIntent] = useMutation(CREATE_PAYMENT_INTENT);
  const [savePayment] = useMutation(SAVE_PAYMENT);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (event) => {
    event.preventDefault();
    setLoading(true);

    try {
      const { data } = await createPaymentIntent({ variables: { bookingId } });
      const clientSecret = data.createPaymentIntent.clientSecret;

      const cardElement = elements.getElement(CardElement);
      const paymentResult = await stripe.confirmCardPayment(clientSecret, {
        payment_method: { card: cardElement },
      });

      if (paymentResult.error) {
        setError(`Payment failed: ${paymentResult.error.message}`);
      } else if (paymentResult.paymentIntent.status === 'succeeded') {
        await savePayment({ variables: { bookingId, paymentIntentId: paymentResult.paymentIntent.id } });
        alert('Payment successful!');
      }
    } catch (error) {
      setError(`Payment failed: ${error.message}`);
    }

    setLoading(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit" disabled={!stripe || loading}>
        {loading ? 'Processing...' : 'Pay'}
      </button>
      {error && <div>{error}</div>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. GraphQL Queries and Mutations

Define the necessary queries and mutations for creating payment intents and saving payments.

graphql/queries.js

import { gql } from '@apollo/client';

export const CREATE_PAYMENT_INTENT = gql`
  mutation CreatePaymentIntent($bookingId: ID!) {
    createPaymentIntent(bookingId: $bookingId) {
      clientSecret
    }
  }
`;

export const SAVE_PAYMENT = gql`
  mutation SavePayment($bookingId: ID!, $paymentIntentId: String!) {
    savePayment(bookingId: $bookingId, paymentIntentId: $paymentIntentId) {
      id
      booking {
        id
        flight {
          airline
          from
          to
        }
      }
      amount
      currency
      status
    }
  }
`;

export const GET_PAYMENT_HISTORY = gql`
  query GetPaymentHistory($userId: ID!) {
    paymentHistory(userId: $userId) {
      id
      amount
      currency
      status
      createdAt
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

5. Payment Page

Create a page to handle payment processing.

pages/payment.js

import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import CheckoutForm from '../components/CheckoutForm';
import { useRouter } from 'next/router';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

export default function Payment() {
  const router = useRouter();
  const { bookingId } = router.query;

  return (
    <div>
      <h1>Complete Your Payment</h1>
      <Elements stripe={stripePromise}>
        <CheckoutForm bookingId={bookingId} />
      </Elements>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. View Payment History

Create a page to view the user's payment history.

pages/payment-history.js

import { useQuery } from '@apollo/client';
import { GET_PAYMENT_HISTORY } from '../graphql/queries';
import { useRouter } from 'next/router';

export default function PaymentHistory({ userId }) {
  const { data, loading, error } = useQuery(GET_PAYMENT_HISTORY, { variables: { userId } });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Payment History</h2>
      {data.paymentHistory.map((payment) => (
        <div key={payment.id}>
          <p>Amount: {payment.amount} {payment.currency}</p>
          <p>Status: {payment.status}</p>
          <p>Date: {new Date(payment.createdAt).toLocaleString()}</p>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

7. Environment Variables

Ensure you have your Stripe publishable key set in your environment variables.

.env.local

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=your-stripe-publishable-key
Enter fullscreen mode Exit fullscreen mode

8. Backend Integration (Necessary for Full Implementation)

While this guide focuses on the frontend, note that you must implement the corresponding backend logic in your NestJS application to handle:

  1. Creating payment intents using Stripe.
  2. Saving payment information after successful payments.
  3. Providing payment history data via GraphQL queries.

Final Steps

  1. Ensure your backend is properly set up to handle Stripe payments and is accessible to your frontend.
  2. Test the entire flow from searching flights, booking, making payments, and viewing payment history.

This setup should provide a robust foundation for integrating payment gateways into your flight booking system's frontend using Next.js.

Email and SMS Notifications

To handle notifications, we will integrate with services like SendGrid for emails and Twilio for SMS notifications. We'll create components and hooks to send these notifications when needed. The actual sending logic will typically be handled by your backend, but we can trigger these notifications from the frontend.

Reviews and Ratings

We'll create components to rate and review airlines and flights, and display reviews and ratings from other users.

Step-by-Step Implementation

1. Initialize Project and Install Dependencies

If you haven't already, create a Next.js project and install the necessary dependencies:

npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
npm install @apollo/client graphql react-hook-form date-fns
Enter fullscreen mode Exit fullscreen mode

2. Set Up Apollo Client

Set up Apollo Client as shown in the previous sections.

3. GraphQL Queries and Mutations

Define the necessary queries and mutations for sending notifications, creating reviews, and fetching reviews.

graphql/queries.js

import { gql } from '@apollo/client';

export const SEND_NOTIFICATION = gql`
  mutation SendNotification($type: String!, $to: String!, $message: String!) {
    sendNotification(type: $type, to: $to, message: $message) {
      success
      message
    }
  }
`;

export const CREATE_REVIEW = gql`
  mutation CreateReview($flightId: ID!, $rating: Int!, $comment: String!) {
    createReview(flightId: $flightId, rating: $rating, comment: $comment) {
      id
      flightId
      rating
      comment
      user {
        id
        username
      }
    }
  }
`;

export const GET_REVIEWS = gql`
  query GetReviews($flightId: ID!) {
    reviews(flightId: $flightId) {
      id
      rating
      comment
      user {
        id
        username
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

4. Notification Hook

Create a hook to send notifications.

hooks/useNotification.js

import { useMutation } from '@apollo/client';
import { SEND_NOTIFICATION } from '../graphql/queries';

export const useNotification = () => {
  const [sendNotification, { loading, error }] = useMutation(SEND_NOTIFICATION);

  const notify = async (type, to, message) => {
    try {
      const response = await sendNotification({ variables: { type, to, message } });
      return response.data.sendNotification;
    } catch (error) {
      console.error('Notification error:', error);
      throw new Error('Failed to send notification');
    }
  };

  return { notify, loading, error };
};
Enter fullscreen mode Exit fullscreen mode

5. Notification Component

Create a component to send notifications.

components/Notification.js

import { useForm } from 'react-hook-form';
import { useNotification } from '../hooks/useNotification';

export default function Notification() {
  const { register, handleSubmit } = useForm();
  const { notify, loading, error } = useNotification();

  const onSubmit = async (formData) => {
    const { type, to, message } = formData;
    try {
      await notify(type, to, message);
      alert('Notification sent successfully!');
    } catch (error) {
      alert('Failed to send notification');
    }
  };

  return (
    <div>
      <h2>Send Notification</h2>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label>Type (email/sms):</label>
          <input type="text" {...register('type')} required />
        </div>
        <div>
          <label>To:</label>
          <input type="text" {...register('to')} required />
        </div>
        <div>
          <label>Message:</label>
          <textarea {...register('message')} required />
        </div>
        <button type="submit" disabled={loading}>
          {loading ? 'Sending...' : 'Send'}
        </button>
        {error && <p>Error: {error.message}</p>}
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Review and Rating Components

Create components to handle reviews and ratings.

components/ReviewForm.js

import { useForm } from 'react-hook-form';
import { useMutation } from '@apollo/client';
import { CREATE_REVIEW } from '../graphql/queries';

export default function ReviewForm({ flightId, onReviewSubmitted }) {
  const { register, handleSubmit } = useForm();
  const [createReview, { loading, error }] = useMutation(CREATE_REVIEW);

  const onSubmit = async (formData) => {
    const { rating, comment } = formData;
    try {
      const response = await createReview({ variables: { flightId, rating: parseInt(rating), comment } });
      onReviewSubmitted(response.data.createReview);
    } catch (error) {
      console.error('Review submission error:', error);
    }
  };

  return (
    <div>
      <h2>Submit a Review</h2>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label>Rating (1-5):</label>
          <input type="number" {...register('rating')} min="1" max="5" required />
        </div>
        <div>
          <label>Comment:</label>
          <textarea {...register('comment')} required />
        </div>
        <button type="submit" disabled={loading}>
          {loading ? 'Submitting...' : 'Submit'}
        </button>
        {error && <p>Error: {error.message}</p>}
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

components/Reviews.js

import { useQuery } from '@apollo/client';
import { GET_REVIEWS } from '../graphql/queries';

export default function Reviews({ flightId }) {
  const { data, loading, error } = useQuery(GET_REVIEWS, { variables: { flightId } });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Reviews</h2>
      {data.reviews.length > 0 ? (
        data.reviews.map((review) => (
          <div key={review.id}>
            <p>Rating: {review.rating}</p>
            <p>Comment: {review.comment}</p>
            <p>User: {review.user.username}</p>
          </div>
        ))
      ) : (
        <p>No reviews yet.</p>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

7. Integrate Components into Pages

pages/flight/[id].js

import { useRouter } from 'next/router';
import { useState } from 'react';
import ReviewForm from '../../components/ReviewForm';
import Reviews from '../../components/Reviews';

export default function FlightDetails() {
  const router = useRouter();
  const { id } = router.query;
  const [reviews, setReviews] = useState([]);

  const handleReviewSubmitted = (newReview) => {
    setReviews((prevReviews) => [...prevReviews, newReview]);
  };

  return (
    <div>
      <h1>Flight Details</h1>
      <p>Flight ID: {id}</p>
      {/* Render flight details here */}
      <ReviewForm flightId={id} onReviewSubmitted={handleReviewSubmitted} />
      <Reviews flightId={id} reviews={reviews} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

pages/send-notification.js

import Notification from '../components/Notification';

export default function SendNotificationPage() {
  return (
    <div>
      <h1>Send Notification</h1>
      <Notification />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Final Steps

  1. Ensure your backend is properly set up to handle notifications and reviews.
  2. Test the entire flow for sending notifications, submitting reviews, and viewing reviews.
  3. Enhance the UI/UX as needed and handle edge cases and error scenarios gracefully.

This setup provides a robust foundation for handling notifications and reviews/ratings in your flight booking system's frontend using Next.js.

To implement the admin features for your flight booking system, we'll create components for the dashboard, flight management (adding, editing, and deleting flights), and managing flight schedules and availability. We'll use Apollo Client for GraphQL queries and mutations to interact with the backend.

1. Initialize Project and Install Dependencies

If you haven't already, create a Next.js project and install the necessary dependencies:

npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
npm install @apollo/client graphql react-hook-form
Enter fullscreen mode Exit fullscreen mode

2. Set Up Apollo Client

Set up Apollo Client as shown in the previous sections.

3. GraphQL Queries and Mutations

Define the necessary queries and mutations for managing flights and getting the dashboard overview.

graphql/queries.js

import { gql } from '@apollo/client';

export const GET_DASHBOARD_OVERVIEW = gql`
  query GetDashboardOverview {
    dashboardOverview {
      bookingsCount
      usersCount
      flightsCount
      revenue
    }
  }
`;

export const GET_FLIGHTS = gql`
  query GetFlights {
    flights {
      id
      airline
      from
      to
      departureTime
      arrivalTime
      availableSeats
    }
  }
`;

export const CREATE_FLIGHT = gql`
  mutation CreateFlight($input: FlightInput!) {
    createFlight(input: $input) {
      id
      airline
      from
      to
      departureTime
      arrivalTime
      availableSeats
    }
  }
`;

export const UPDATE_FLIGHT = gql`
  mutation UpdateFlight($id: ID!, $input: FlightInput!) {
    updateFlight(id: $id, input: $input) {
      id
      airline
      from
      to
      departureTime
      arrivalTime
      availableSeats
    }
  }
`;

export const DELETE_FLIGHT = gql`
  mutation DeleteFlight($id: ID!) {
    deleteFlight(id: $id) {
      success
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

4. Dashboard Component

Create a component to display the dashboard overview.

components/Dashboard.js

import { useQuery } from '@apollo/client';
import { GET_DASHBOARD_OVERVIEW } from '../graphql/queries';

export default function Dashboard() {
  const { data, loading, error } = useQuery(GET_DASHBOARD_OVERVIEW);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const { bookingsCount, usersCount, flightsCount, revenue } = data.dashboardOverview;

  return (
    <div>
      <h1>Admin Dashboard</h1>
      <div>
        <h3>Overview</h3>
        <p>Bookings: {bookingsCount}</p>
        <p>Users: {usersCount}</p>
        <p>Flights: {flightsCount}</p>
        <p>Revenue: ${revenue}</p>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Flight Management Components

Create components to manage flights: adding, editing, and deleting flights.

components/FlightForm.js

import { useForm } from 'react-hook-form';
import { useMutation } from '@apollo/client';
import { CREATE_FLIGHT, UPDATE_FLIGHT } from '../graphql/queries';

export default function FlightForm({ flight, onCompleted }) {
  const { register, handleSubmit, reset } = useForm({
    defaultValues: flight || { airline: '', from: '', to: '', departureTime: '', arrivalTime: '', availableSeats: 0 },
  });
  const [createFlight] = useMutation(CREATE_FLIGHT, { onCompleted });
  const [updateFlight] = useMutation(UPDATE_FLIGHT, { onCompleted });

  const onSubmit = async (formData) => {
    if (flight) {
      await updateFlight({ variables: { id: flight.id, input: formData } });
    } else {
      await createFlight({ variables: { input: formData } });
    }
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Airline:</label>
        <input type="text" {...register('airline')} required />
      </div>
      <div>
        <label>From:</label>
        <input type="text" {...register('from')} required />
      </div>
      <div>
        <label>To:</label>
        <input type="text" {...register('to')} required />
      </div>
      <div>
        <label>Departure Time:</label>
        <input type="datetime-local" {...register('departureTime')} required />
      </div>
      <div>
        <label>Arrival Time:</label>
        <input type="datetime-local" {...register('arrivalTime')} required />
      </div>
      <div>
        <label>Available Seats:</label>
        <input type="number" {...register('availableSeats')} required />
      </div>
      <button type="submit">{flight ? 'Update' : 'Add'} Flight</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

components/FlightList.js

import { useQuery, useMutation } from '@apollo/client';
import { GET_FLIGHTS, DELETE_FLIGHT } from '../graphql/queries';

export default function FlightList({ onEdit }) {
  const { data, loading, error } = useQuery(GET_FLIGHTS);
  const [deleteFlight] = useMutation(DELETE_FLIGHT, {
    refetchQueries: [{ query: GET_FLIGHTS }],
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const handleDelete = async (id) => {
    await deleteFlight({ variables: { id } });
  };

  return (
    <div>
      <h2>Flights</h2>
      <ul>
        {data.flights.map((flight) => (
          <li key={flight.id}>
            <div>
              <p>Airline: {flight.airline}</p>
              <p>From: {flight.from}</p>
              <p>To: {flight.to}</p>
              <p>Departure Time: {new Date(flight.departureTime).toLocaleString()}</p>
              <p>Arrival Time: {new Date(flight.arrivalTime).toLocaleString()}</p>
              <p>Available Seats: {flight.availableSeats}</p>
              <button onClick={() => onEdit(flight)}>Edit</button>
              <button onClick={() => handleDelete(flight.id)}>Delete</button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Integrate Components into Pages

pages/admin/dashboard.js

import Dashboard from '../../components/Dashboard';

export default function AdminDashboardPage() {
  return (
    <div>
      <Dashboard />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

pages/admin/flights.js

import { useState } from 'react';
import FlightForm from '../../components/FlightForm';
import FlightList from '../../components/FlightList';

export default function AdminFlightsPage() {
  const [selectedFlight, setSelectedFlight] = useState(null);

  const handleEdit = (flight) => {
    setSelectedFlight(flight);
  };

  const handleFormCompleted = () => {
    setSelectedFlight(null);
  };

  return (
    <div>
      <h1>Manage Flights</h1>
      <FlightForm flight={selectedFlight} onCompleted={handleFormCompleted} />
      <FlightList onEdit={handleEdit} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Final Steps

  1. Ensure your backend is properly set up to handle flight management and provide dashboard data.
  2. Test the entire flow for adding, editing, deleting flights, and viewing the dashboard.
  3. Enhance the UI/UX as needed and handle edge cases and error scenarios gracefully.

This setup provides a robust foundation for managing flights and viewing dashboard data in your flight booking system's frontend using Next.js.

To implement the user management, booking management, and reporting and analytics features for your flight booking system, we'll create components for each feature and integrate them into your Next.js application. We'll use Apollo Client for GraphQL queries and mutations to interact with the backend.

1. Initialize Project and Install Dependencies

If you haven't already, create a Next.js project and install the necessary dependencies:

npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
npm install @apollo/client graphql react-hook-form
Enter fullscreen mode Exit fullscreen mode

2. Set Up Apollo Client

Set up Apollo Client as shown in the previous sections.

3. GraphQL Queries and Mutations

Define the necessary queries and mutations for managing users, bookings, and generating reports.

graphql/queries.js

import { gql } from '@apollo/client';

export const GET_USERS = gql`
  query GetUsers {
    users {
      id
      username
      email
      role
    }
  }
`;

export const UPDATE_USER_ROLE = gql`
  mutation UpdateUserRole($id: ID!, $role: String!) {
    updateUserRole(id: $id, role: $role) {
      id
      username
      email
      role
    }
  }
`;

export const GET_BOOKINGS = gql`
  query GetBookings {
    bookings {
      id
      flight {
        id
        airline
        from
        to
      }
      user {
        id
        username
      }
      status
      createdAt
    }
  }
`;

export const UPDATE_BOOKING_STATUS = gql`
  mutation UpdateBookingStatus($id: ID!, $status: String!) {
    updateBookingStatus(id: $id, status: $status) {
      id
      status
    }
  }
`;

export const GET_REPORTS = gql`
  query GetReports {
    reports {
      bookingsCount
      revenue
      userActivity
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

4. User Management Components

Create components to view and manage users and assign roles.

components/UserList.js

import { useQuery, useMutation } from '@apollo/client';
import { GET_USERS, UPDATE_USER_ROLE } from '../graphql/queries';
import { useForm } from 'react-hook-form';

export default function UserList() {
  const { data, loading, error } = useQuery(GET_USERS);
  const [updateUserRole] = useMutation(UPDATE_USER_ROLE, {
    refetchQueries: [{ query: GET_USERS }],
  });
  const { register, handleSubmit } = useForm();

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const onSubmit = async (formData) => {
    await updateUserRole({ variables: { id: formData.id, role: formData.role } });
  };

  return (
    <div>
      <h2>User Management</h2>
      <ul>
        {data.users.map((user) => (
          <li key={user.id}>
            <p>Username: {user.username}</p>
            <p>Email: {user.email}</p>
            <p>Role: {user.role}</p>
            <form onSubmit={handleSubmit(onSubmit)}>
              <input type="hidden" value={user.id} {...register('id')} />
              <select {...register('role')}>
                <option value="admin">Admin</option>
                <option value="customer_support">Customer Support</option>
                <option value="user">User</option>
              </select>
              <button type="submit">Update Role</button>
            </form>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Booking Management Components

Create components to view and manage bookings.

components/BookingList.js

import { useQuery, useMutation } from '@apollo/client';
import { GET_BOOKINGS, UPDATE_BOOKING_STATUS } from '../graphql/queries';
import { useForm } from 'react-hook-form';

export default function BookingList() {
  const { data, loading, error } = useQuery(GET_BOOKINGS);
  const [updateBookingStatus] = useMutation(UPDATE_BOOKING_STATUS, {
    refetchQueries: [{ query: GET_BOOKINGS }],
  });
  const { register, handleSubmit } = useForm();

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const onSubmit = async (formData) => {
    await updateBookingStatus({ variables: { id: formData.id, status: formData.status } });
  };

  return (
    <div>
      <h2>Booking Management</h2>
      <ul>
        {data.bookings.map((booking) => (
          <li key={booking.id}>
            <p>Flight: {booking.flight.airline} from {booking.flight.from} to {booking.flight.to}</p>
            <p>User: {booking.user.username}</p>
            <p>Status: {booking.status}</p>
            <form onSubmit={handleSubmit(onSubmit)}>
              <input type="hidden" value={booking.id} {...register('id')} />
              <select {...register('status')}>
                <option value="confirmed">Confirmed</option>
                <option value="cancelled">Cancelled</option>
              </select>
              <button type="submit">Update Status</button>
            </form>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Reporting and Analytics Components

Create components to generate reports and display analytics.

components/Reports.js

import { useQuery } from '@apollo/client';
import { GET_REPORTS } from '../graphql/queries';

export default function Reports() {
  const { data, loading, error } = useQuery(GET_REPORTS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const { bookingsCount, revenue, userActivity } = data.reports;

  return (
    <div>
      <h2>Reports and Analytics</h2>
      <div>
        <p>Bookings Count: {bookingsCount}</p>
        <p>Revenue: ${revenue}</p>
        <p>User Activity: {userActivity}</p>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

7. Integrate Components into Pages

pages/admin/users.js

import UserList from '../../components/UserList';

export default function AdminUsersPage() {
  return (
    <div>
      <h1>Manage Users</h1>
      <UserList />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

pages/admin/bookings.js

import BookingList from '../../components/BookingList';

export default function AdminBookingsPage() {
  return (
    <div>
      <h1>Manage Bookings</h1>
      <BookingList />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

pages/admin/reports.js

import Reports from '../../components/Reports';

export default function AdminReportsPage() {
  return (
    <div>
      <h1>Reports and Analytics</h1>
      <Reports />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Final Steps

  1. Ensure your backend is properly set up to handle user management, booking management, and generating reports.
  2. Test the entire flow for viewing and managing users, bookings, and generating reports.
  3. Enhance the UI/UX as needed and handle edge cases and error scenarios gracefully.

This setup provides a robust foundation for managing users, bookings, and generating reports in your flight booking system's frontend using Next.js.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content is generated by AI.

Top comments (1)

Collapse
 
manish108 profile image
Manish Kumar

Please make a tutorial video on how to create flight booking web app using next js and fetching flight data from api