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
-
User Registration and Authentication:
- Sign up, login, and logout.
- Password recovery/reset.
- Social media login (Google, Facebook, etc.).
-
User Profile:
- View and edit profile information.
- Upload profile picture.
-
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.
-
Payment Integration:
- Integration with payment gateways (PayPal, Stripe, etc.).
- Secure payment processing.
- View payment history.
-
Notifications:
- Email and SMS notifications for booking confirmations, flight status updates, etc.
-
Reviews and Ratings:
- Rate and review airlines and flights.
- View reviews and ratings from other users.
Admin Features
-
Dashboard:
- Overview of bookings, users, flights, and revenue.
-
Flight Management:
- Add, edit, and delete flights.
- Manage flight schedules and availability.
-
User Management:
- View and manage users.
- Assign roles (e.g., admin, customer support).
-
Booking Management:
- View and manage all bookings.
- Cancel or modify bookings.
-
Reporting and Analytics:
- Generate reports on bookings, revenue, user activity, etc.
- Analyze data to make informed decisions.
Functionalities
-
API Integration:
- Integrate with third-party APIs for real-time flight data (e.g., Amadeus, Skyscanner).
-
Security:
- Implement authentication and authorization (JWT, OAuth).
- Secure data transmission (HTTPS).
-
Performance Optimization:
- Optimize database queries.
- Implement caching mechanisms.
- Use CDN for static assets.
-
Scalability:
- Design for horizontal scalability.
- Use microservices architecture if needed.
-
Testing:
- Unit and integration tests for back-end (NestJS).
- End-to-end tests for the front end (Next.js).
-
Documentation:
- API documentation (Swagger).
- User manuals and help sections.
Optional Advanced Features
-
Multi-language Support:
- Provide interface and notifications in multiple languages.
-
Loyalty Program:
- Implement a points or rewards system for frequent flyers.
-
Customer Support:
- Live chat or chatbot integration.
- Ticketing system for support requests.
-
Mobile App:
- Develop a companion mobile app (using React Native or Flutter).
-
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
2. Install Required Packages
You'll need some additional packages for authentication:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs
3. Create Auth Module
Generate the authentication module and related components:
nest generate module auth
nest generate service auth
nest generate controller auth
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;
}
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),
};
}
}
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);
}
}
7. DTOs
Define the DTOs (Data Transfer Objects) for authentication:
// src/auth/dto/auth-credentials.dto.ts
export class AuthCredentialsDto {
email: string;
password: string;
}
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;
}
}
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);
}
}
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 {}
11. Environment Configuration
Finally, add the JWT secret to your environment variables:
# .env
JWT_SECRET=your_jwt_secret_key
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 {}
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;
}
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);
}
}
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;
}
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);
}
}
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 {}
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 {}
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;
}
// 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;
}
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;
}
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);
}
}
// 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'],
});
}
}
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);
}
}
// 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);
}
}
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 {}
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 {}
// 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 {}
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);
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
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
3. Create Payment Module
Generate the payment module and related components:
nest generate module payment
nest generate service payment
nest generate controller payment
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;
}
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' },
});
}
}
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);
}
}
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 {}
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 {}
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>
// 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);
};
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
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
3. Create Notification Module
Generate the notification module and related components:
nest generate module notification
nest generate service notification
nest generate controller notification
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
}
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'],
});
}
}
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 {}
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 {}
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;
}
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
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' },
});
}
}
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' },
});
}
}
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;
}
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);
}
}
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 {}
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 {}
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
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));
}
}
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);
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,
};
}
}
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();
}
}
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`);
}
}
}
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[];
}
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);
}
}
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.
- Initialize NestJS Project
First, create a new NestJS project:
nest new flight-booking-backend
cd flight-booking-backend
- 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
- 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 {}
- 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;
}
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 {}
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 });
}
}
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 };
}
}
- 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;
}
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 {}
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);
}
}
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);
}
}
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;
}
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 {}
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);
}
}
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);
}
}
- 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);
}
}
roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
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);
}
}
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.
- Initialize Next.js Project
First, create a new Next.js project:
npx create-next-app@latest flight-booking-frontend
cd flight-booking-frontend
- Install Required Packages
Install the necessary packages:
npm install @apollo/client graphql next-auth @mantine/core @mantine/hooks @mantine/dropzone
- 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;
- 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
}
}
`;
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>
);
}
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>
);
}
- 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>
);
}
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
}
}
`;
- Upload Profile Picture
Use Mantine for the file upload component. Install Mantine and set up the file upload:
npm install @mantine/core @mantine/dropzone
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>
);
}
graphql/queries.js
import
{ gql } from '@apollo/client';
export const UPLOAD_PROFILE_PICTURE = gql`
mutation UploadProfilePicture($file: Upload!) {
uploadProfilePicture(file: $file) {
url
}
}
`;
- 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>
);
}
- Social Media Login
Set up social media login using next-auth
:
npm install next-auth
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;
},
},
});
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
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>
);
}
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
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;
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
}
}
`;
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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
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>
);
}
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
}
}
`;
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>
);
}
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>
);
}
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
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:
- Creating payment intents using Stripe.
- Saving payment information after successful payments.
- Providing payment history data via GraphQL queries.
Final Steps
- Ensure your backend is properly set up to handle Stripe payments and is accessible to your frontend.
- 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
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
}
}
}
`;
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 };
};
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>
);
}
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>
);
}
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>
);
}
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>
);
}
pages/send-notification.js
import Notification from '../components/Notification';
export default function SendNotificationPage() {
return (
<div>
<h1>Send Notification</h1>
<Notification />
</div>
);
}
Final Steps
- Ensure your backend is properly set up to handle notifications and reviews.
- Test the entire flow for sending notifications, submitting reviews, and viewing reviews.
- 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
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
}
}
`;
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>
);
}
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>
);
}
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>
);
}
6. Integrate Components into Pages
pages/admin/dashboard.js
import Dashboard from '../../components/Dashboard';
export default function AdminDashboardPage() {
return (
<div>
<Dashboard />
</div>
);
}
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>
);
}
Final Steps
- Ensure your backend is properly set up to handle flight management and provide dashboard data.
- Test the entire flow for adding, editing, deleting flights, and viewing the dashboard.
- 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
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
}
}
`;
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>
);
}
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>
);
}
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>
);
}
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>
);
}
pages/admin/bookings.js
import BookingList from '../../components/BookingList';
export default function AdminBookingsPage() {
return (
<div>
<h1>Manage Bookings</h1>
<BookingList />
</div>
);
}
pages/admin/reports.js
import Reports from '../../components/Reports';
export default function AdminReportsPage() {
return (
<div>
<h1>Reports and Analytics</h1>
<Reports />
</div>
);
}
Final Steps
- Ensure your backend is properly set up to handle user management, booking management, and generating reports.
- Test the entire flow for viewing and managing users, bookings, and generating reports.
- 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)
Please make a tutorial video on how to create flight booking web app using next js and fetching flight data from api