Building a Scalable Pet Adoption Platform with Next.js, NestJS, PostgreSQL, and Tailwind CSS

nadim_ch0wdhury

Nadim Chowdhury

Posted on October 10, 2024

Building a Scalable Pet Adoption Platform with Next.js, NestJS, PostgreSQL, and Tailwind CSS

To develop the Pet Adoption Platform using Next.js, Tailwind CSS, NestJS, and PostgreSQL, I will outline the architecture, features, and required functionalities based on your requirements for front-end, back-end, and mobile app development.

Architecture Overview:

  • Front-end: Developed using Next.js with Tailwind CSS for UI design.
  • Back-end: Built using NestJS as the framework with PostgreSQL as the database.
  • Mobile App: Implemented using React Native or a similar cross-platform framework, enabling pet browsing, application, and notifications for adoption updates.

Project Breakdown

Front-End (Next.js with Tailwind CSS)

  1. Search for Adoptable Pets:

    • Implement a search feature using Next.js' file-based routing.
    • UI Components:
      • Search bar.
      • Pet listing cards displaying details like name, breed, and location.
    • Functionalities:
      • Fetch adoptable pets from the back-end.
      • Filtering options (e.g., breed, age, location).
      • Integration with back-end APIs to retrieve data.
  2. Pet Filtering:

    • Filters:
      • Location: Drop-down to filter by geographic area.
      • Breed: Searchable list to filter by specific breeds.
      • Age and size: Optional filters for narrowing results.
    • Use Next.js dynamic routing to create detail pages for each pet.
  3. Responsive Design:

    • Use Tailwind CSS for responsive layouts that adapt across desktop and mobile devices.
  4. Adoption Form:

    • Implement a form using React Hook Form or Formik for capturing user information and pet application details.

Back-End (NestJS with PostgreSQL)

  1. User Registration and Authentication:

    • Use NestJS modules for user authentication (JWT-based).
    • Include registration, login, and password recovery features.
    • Implement role-based access control (e.g., admin, user) for secure data management.
  2. Pet Listings Management:

    • CRUD operations for admins to add, edit, or remove pet listings.
    • Entities and Services in NestJS for pet-related data:
      • Pet Entity: Stores information like breed, age, health status, etc.
      • Pet Service: Manages business logic for pet listings.
    • Integration with PostgreSQL using TypeORM.
  3. Adoption Forms:

    • Store and process user submissions in PostgreSQL.
    • Implement status tracking (e.g., "application pending", "approved").
  4. Notification System:

    • Push notifications or emails to inform users about adoption status.
    • Use Firebase Cloud Messaging (FCM) or Email Service for notifications.
  5. API Structure:

    • Modular architecture in NestJS with distinct modules for users, pets, and adoption forms.
    • API routes for searching, listing pets, and handling adoption applications.

Mobile App (React Native with NestJS API)

  1. Pet Browsing:

    • Similar UI and functionality as the web front-end but adapted for mobile experience.
    • Offline support: Cache data locally using a solution like AsyncStorage.
  2. Push Notifications:

    • Integrate Firebase Cloud Messaging (FCM) for mobile push notifications to alert users about new pets and application updates.
  3. Adoption Applications:

    • Allow users to submit adoption applications via the mobile app.
    • Send notifications or email confirmations after form submissions.

Database Schema (PostgreSQL)

  1. User Table:

    • Fields: id, name, email, passwordHash, role.
    • Relations: Users to adoption applications (One-to-Many).
  2. Pet Table:

    • Fields: id, name, breed, age, status, location.
    • Relations: Pets to applications (One-to-Many).
  3. Adoption Application Table:

    • Fields: id, userId, petId, status, comments.

Sample Folder Structure

Front-End (Next.js)

/pages
  /adoptable-pets
    [petId].tsx   # Pet details page
/components
  SearchBar.tsx
  PetCard.tsx
/styles
  globals.css     # Tailwind CSS configuration
Enter fullscreen mode Exit fullscreen mode

Back-End (NestJS)

/src
  /modules
    /users
      users.controller.ts
      users.service.ts
      user.entity.ts
    /pets
      pets.controller.ts
      pets.service.ts
      pet.entity.ts
    /adoption
      adoption.controller.ts
      adoption.service.ts
      adoption.entity.ts
Enter fullscreen mode Exit fullscreen mode

Mobile App (React Native)

/components
  PetList.tsx
  AdoptionForm.tsx
/screens
  HomeScreen.tsx
  PetDetailScreen.tsx
Enter fullscreen mode Exit fullscreen mode

Key Functionalities

  1. Front-End:

    • Search adoptable pets by filters (location, breed).
    • View detailed pet profiles.
    • Apply for pet adoptions through forms.
  2. Back-End:

    • User authentication and management.
    • Admin CRUD for pet listings.
    • Submission and tracking of adoption forms.
  3. Mobile App:

    • Browse pets and apply for adoptions.
    • Get notifications for updates.

You can start building the project by breaking down the tasks based on the modules above and iteratively integrating the functionality between the front-end, back-end, and mobile app. If you'd like detailed setup instructions or code snippets, feel free to ask!

For a Pet Adoption Platform using Next.js, Tailwind CSS, NestJS, and PostgreSQL, there are numerous features you can include depending on your goals, time, and resources. Below is a list of potential features, categorized into core and optional functionalities. You can pick and choose based on the scope of your project.

Core Features (Must-Have)

These are the essential features required for the MVP (Minimum Viable Product):

1. User Authentication and Profile Management

  • User Registration and Login: Users can register and log in using email and password.
  • Profile Management: Users can manage their profile, update details, and view adoption history.

2. Pet Listings and Search

  • Browse Pets: Display a list of adoptable pets with images, breed, location, and other details.
  • Search: Search for pets by name, breed, or other characteristics.
  • Filter Pets: Filter pets by location, breed, age, size, or availability status.

3. Pet Details Page

  • Detailed Pet Information: Each pet should have a detailed profile page with additional information like age, vaccination status, temperament, and adoption requirements.
  • Image Gallery: Display multiple images of each pet.

4. Adoption Application Form

  • Submit Adoption Requests: Allow users to submit an adoption request for a pet through a form.
  • Application Status: Users can check the status of their applications (pending, approved, rejected).

5. Admin Dashboard

  • CRUD for Pet Listings: Admins can add, edit, or delete pet listings.
  • Manage Adoption Applications: Admins can view, approve, or reject adoption applications.

6. Responsive Design

  • Mobile-Friendly UI: Ensure the website is fully responsive and works seamlessly on mobile devices.

Optional Features (Nice-to-Have)

These features can add extra value and improve the user experience:

1. User Notifications

  • Email Notifications: Send users confirmation emails when they submit an adoption application or when the application status changes.
  • Push Notifications: Notify users of new adoptable pets or changes in their application status (can be integrated using services like Firebase).

2. Favorite/Wishlist Pets

  • Save Pets for Later: Allow users to save pets to a wishlist for easy access later.

3. Social Sharing

  • Share Pet Profiles: Enable users to share pet profiles via social media platforms like Facebook, Twitter, or email.

4. Real-Time Chat or Messaging

  • Chat with Admins or Shelters: Users can chat with admins or shelters to ask questions about the adoption process or a particular pet.

5. Donation Integration

  • Donations: Allow users to make donations to the shelter or rescue organization.

6. Adoption Events and News

  • Event Calendar: Display upcoming adoption events (in-person or virtual) hosted by the shelter.
  • News Section: Share updates and news about the organization, success stories, etc.

7. Advanced Filtering

  • More Detailed Filters: Add more advanced filtering options such as coat color, temperament, energy level, or special needs.

8. User Reviews and Testimonials

  • Review Section: Let users who have adopted pets leave reviews or testimonials about their experience.

9. Referral Program

  • Invite Friends: Users can invite their friends to the platform, earning rewards or discounts on future adoptions.

10. Location-Based Features

  • Find Nearby Shelters: Users can search for pets based on their proximity to local shelters.
  • Geolocation: Allow users to filter by pets within a certain radius of their current location.

Future Feature Ideas (Advanced)

These features can be developed once the core functionality is stable and your platform is growing:

1. Mobile App Integration

  • React Native App: Develop a mobile app to enhance the user experience and accessibility.
  • Push Notifications for Mobile: Enable real-time notifications for mobile users.

2. Shelter Portal

  • Shelter Management System: Allow shelters to manage their own pet listings, view applications, and communicate with potential adopters.

3. AI/ML-Based Pet Recommendation

  • Pet Matching Algorithm: Use AI/ML to recommend pets to users based on their preferences and previous interactions.

4. Success Stories and Blog

  • Success Stories: Feature success stories from users who have successfully adopted pets through the platform.
  • Blog: Add a blog section for pet care tips, adoption advice, and updates.

5. Payment Integration for Adoption Fees

  • Payment Gateway: Integrate a payment gateway for users to pay adoption fees directly on the platform.

6. Pet Health Records

  • Track Vaccination and Health: Store health and vaccination records for each pet that adopters can access.

Summary

For the initial version of the project, focusing on Core Features will provide a fully functional platform. Once those are stable, you can consider adding Optional Features and then gradually implement Future Feature Ideas based on user feedback and platform growth.

By prioritizing features based on user needs and development time, you can ensure your project remains manageable while still delivering value to your users.

Here’s a detailed frontend file and folder structure for the Pet Adoption Platform with all the core, optional, and future functionalities included. The structure is designed for Next.js, Tailwind CSS, and best practices in modularity, scalability, and maintainability.

Full Folder Structure

├── public/                    # Publicly accessible files (images, icons, etc.)
│   ├── images/                # Store static images for pets, logos, etc.
│   └── favicon.ico            # Site favicon
├── src/                       # Project source folder
│   ├── components/            # Reusable components
│   │   ├── Adoption/          # Adoption-related components
│   │   │   ├── AdoptionForm.tsx
│   │   │   ├── ApplicationStatus.tsx
│   │   │   ├── ApplicationList.tsx
│   │   └── Pet/               # Pet-related components
│   │       ├── PetCard.tsx    # Pet card for list display
│   │       ├── PetDetails.tsx # Detailed view of a pet
│   │       ├── PetFilters.tsx # Filtering panel for searching
│   │       └── PetList.tsx    # List view of all pets
│   ├── hooks/                 # Custom hooks
│   │   └── useFetch.ts        # Fetch data hook
│   ├── layout/                # Shared layout components
│   │   ├── Header.tsx         # Header (Navbar)
│   │   ├── Footer.tsx         # Footer component
│   │   └── Sidebar.tsx        # Admin/Sidebar menu
│   ├── pages/                 # Next.js pages (routing)
│   │   ├── api/               # API routes for server-side handling
│   │   │   ├── pets/          # API for pet operations
│   │   │   ├── users/         # API for user-related actions
│   │   ├── adoptable-pets/    # Dynamic routes for pets
│   │   │   └── [id].tsx       # Individual pet detail page
│   │   ├── account/           # User account management
│   │   │   ├── profile.tsx    # User profile management page
│   │   │   └── applications.tsx  # View user's submitted adoption applications
│   │   ├── admin/             # Admin dashboard
│   │   │   ├── index.tsx      # Admin dashboard main page
│   │   │   ├── pets.tsx       # CRUD operations for pets
│   │   │   ├── applications.tsx # Manage adoption applications
│   │   └── index.tsx          # Home page
│   │   └── adopt.tsx          # Adoption page (form submission)
│   │   └── favorites.tsx      # User's favorite pets (wishlist)
│   │   └── about.tsx          # Static About Us page
│   ├── services/              # Services for API calls
│   │   ├── petService.ts      # API calls for fetching pet data
│   │   ├── userService.ts     # User authentication and profile management services
│   │   └── applicationService.ts # Services for handling adoption applications
│   ├── styles/                # Global styles
│   │   ├── globals.css        # Global Tailwind CSS styles
│   │   └── tailwind.css       # Custom Tailwind styles (if needed)
│   ├── utils/                 # Utility functions
│   │   ├── helpers.ts         # General helper functions (e.g., formatting)
│   │   └── validators.ts      # Form validation helpers
│   └── config/                # Configuration settings
│       └── api.ts             # API base URL and config settings
├── .env.local                 # Environment variables for sensitive data
├── next.config.js             # Next.js configuration
├── tailwind.config.js         # Tailwind CSS configuration
├── postcss.config.js          # PostCSS configuration
├── tsconfig.json              # TypeScript configuration
├── package.json               # Project dependencies and scripts
└── README.md                  # Project documentation
Enter fullscreen mode Exit fullscreen mode

Detailed Breakdown of the Folder Structure

1. Public Folder

  • /public/images/: Contains static assets like images, icons, or logos that will be used in the platform, including pet images and user profile pictures.
  • favicon.ico: Favicon for the platform.

2. Components Folder

  • /components/Adoption/: Contains all the components related to the adoption process.
    • AdoptionForm.tsx: Form for users to submit adoption requests.
    • ApplicationStatus.tsx: Displays the status of a user's adoption application.
    • ApplicationList.tsx: Lists all the applications submitted by a user or managed by an admin.
  • /components/Pet/: Components related to the pet listings and details.
    • PetCard.tsx: Displays the pet's basic information in a card format (e.g., name, breed, and image).
    • PetDetails.tsx: Displays detailed information about a pet on a dedicated page.
    • PetFilters.tsx: Provides filter options for users to search and filter pets by breed, age, and location.
    • PetList.tsx: Renders a list of pets based on search or filter results.

3. Hooks Folder

  • useFetch.ts: Custom hook to handle data fetching using fetch() or axios. This hook will handle API calls to the backend for fetching pets, adoption applications, and user data.

4. Layout Folder

  • Header.tsx: Contains the navigation bar, allowing users to browse through the platform.
  • Footer.tsx: Footer with platform-wide links like About Us, Privacy Policy, etc.
  • Sidebar.tsx: Admin panel sidebar for navigating between different admin functionalities (e.g., managing pets, reviewing applications).

5. Pages Folder

  • /pages/api/: Custom server-side API routes for pets, users, and adoption-related logic. These can be used if you plan to manage some backend logic within the Next.js app.
  • /pages/adoptable-pets/[id].tsx: Dynamic route for individual pet details based on the pet ID.
  • /pages/account/: Handles user account pages:
    • profile.tsx: User profile page where they can update their information.
    • applications.tsx: Displays the applications the user has submitted.
  • /pages/admin/: Admin pages for managing the platform:
    • index.tsx: Admin dashboard overview.
    • pets.tsx: Admin interface to create, edit, and delete pets.
    • applications.tsx: Manage and approve/reject adoption applications.
  • /pages/favorites.tsx: User's wishlist or favorite pets.
  • /pages/about.tsx: Static About Us page.

6. Services Folder

  • petService.ts: Handles all the API calls related to pet data (e.g., fetching pets, filtering, and sorting).
  • userService.ts: Handles user-related API calls, such as authentication, profile updates, etc.
  • applicationService.ts: Manages API calls for submitting and reviewing adoption applications.

7. Styles Folder

  • globals.css: Global CSS, including base Tailwind CSS imports and any custom global styles.
  • tailwind.css: (Optional) Custom Tailwind configuration or specific utility classes.

8. Utils Folder

  • helpers.ts: General helper functions, like formatting dates or handling complex data structures.
  • validators.ts: Validation functions for form input, e.g., email, name validation for the adoption form.

9. Config Folder

  • api.ts: Stores the base URL for API calls and any other API-related configurations.

Core Features Implementation

1. Pet Search and Filter

  • The PetFilters.tsx component handles user input for filtering pets by breed, location, and age. It triggers the API call through useFetch to update the displayed list of pets.

2. Pet List and Details

  • PetList.tsx: Fetches and displays pets based on the search query or filters.
  • PetCard.tsx: Displays each pet in a card format with the essential information and a link to view more details.
  • PetDetails.tsx: Provides a detailed view of each pet, including a button to apply for adoption.

3. Adoption Application

  • AdoptionForm.tsx: A form where users can apply for a specific pet's adoption. The form will validate user inputs and submit the application via the applicationService.ts.

4.

User Profile and Application Tracking

  • Profile.tsx: Allows users to update their profile and view their adoption history.
  • ApplicationList.tsx: Displays a user's submitted adoption applications and their current status.

5. Favorites/Wishlist

  • favorites.tsx: Displays pets the user has favorited or saved for later.

Optional and Future Features Implementation

  • Notifications: Add a Notification.tsx component to display messages like adoption application confirmation or updates.
  • Referral Program: Implement a referral program by adding the referral system in user profiles and generating unique referral links.
  • Geolocation for Nearby Shelters: Use a third-party API to integrate a location-based search for nearby pets.

This file structure and breakdown will help you implement a robust, feature-rich Pet Adoption Platform that is modular, scalable, and easy to maintain.

Here is the backend file and folder structure for the Pet Adoption Platform using NestJS and PostgreSQL. This structure is designed for scalability and modularity, following best practices for building maintainable backend services.

Backend Folder Structure

├── src/
│   ├── common/                 # Shared resources like guards, filters, DTOs, utilities
│   │   ├── dto/                # Shared DTOs
│   │   ├── filters/            # Exception filters
│   │   ├── guards/             # Authorization guards (e.g., roles)
│   │   └── utils/              # Utility functions used across modules
│   ├── config/                 # Configuration files (e.g., environment variables)
│   │   ├── config.module.ts    # Config service for environment variables
│   │   ├── config.service.ts   # Configuration management logic
│   └── database/               # Database-related files (TypeORM, migrations, etc.)
│       ├── entities/           # Database entities for TypeORM
│       │   ├── pet.entity.ts   # Pet entity
│       │   ├── user.entity.ts  # User entity
│       │   ├── application.entity.ts # Adoption application entity
│       ├── migrations/         # TypeORM migration files
│       └── database.module.ts  # Database connection configuration
│   ├── modules/                # Core feature modules
│   │   ├── auth/               # Authentication module (JWT)
│   │   │   ├── auth.controller.ts  # Auth-related routes (login, register)
│   │   │   ├── auth.module.ts      # Auth module declaration
│   │   │   ├── auth.service.ts     # Authentication logic
│   │   │   ├── jwt.strategy.ts     # JWT Strategy for securing routes
│   │   │   ├── local.strategy.ts   # Local strategy (login with email/password)
│   │   │   └── dto/                # DTOs for login, registration, etc.
│   │   ├── users/              # User management module
│   │   │   ├── users.controller.ts # User-related routes
│   │   │   ├── users.module.ts     # User module
│   │   │   ├── users.service.ts    # User management logic (CRUD, profile updates)
│   │   │   ├── user.entity.ts      # User entity (shared with database/entities)
│   │   │   └── dto/                # User-specific DTOs (Data Transfer Objects)
│   │   ├── pets/               # Pets management module
│   │   │   ├── pets.controller.ts  # Routes for handling pet listings
│   │   │   ├── pets.module.ts      # Pet module declaration
│   │   │   ├── pets.service.ts     # Business logic for pets
│   │   │   ├── pet.entity.ts       # Pet entity (shared with database/entities)
│   │   │   └── dto/                # Pet-related DTOs (for validation)
│   │   ├── adoption/           # Adoption management module
│   │   │   ├── adoption.controller.ts  # Routes for submitting and managing applications
│   │   │   ├── adoption.module.ts      # Adoption module declaration
│   │   │   ├── adoption.service.ts     # Business logic for adoption processes
│   │   │   ├── application.entity.ts   # Application entity (shared with database/entities)
│   │   │   └── dto/                    # Adoption-related DTOs
│   │   ├── notifications/       # Notification handling (email, SMS, push notifications)
│   │   │   ├── notifications.module.ts  # Notifications module declaration
│   │   │   ├── notifications.service.ts # Notification logic (for emails, push notifications)
│   │   │   ├── email.service.ts         # Email sending service
│   │   │   └── push.service.ts          # Push notification logic (Firebase integration)
│   │   ├── admin/               # Admin-specific functionality
│   │   │   ├── admin.controller.ts  # Routes for admin operations (approving adoptions, managing pets)
│   │   │   ├── admin.module.ts      # Admin module declaration
│   │   │   └── admin.service.ts     # Admin-specific business logic
│   ├── app.module.ts            # Root application module
│   ├── main.ts                  # Entry point of the application
├── test/                        # Unit and integration tests
│   ├── pets/                    # Test files for pet module
│   │   └── pets.service.spec.ts
│   └── users/                   # Test files for user module
│       └── users.service.spec.ts
├── .env                         # Environment configuration
├── nest-cli.json                # NestJS CLI configuration
├── tsconfig.json                # TypeScript configuration
└── package.json                 # Node.js dependencies and scripts
Enter fullscreen mode Exit fullscreen mode

Detailed Breakdown of Key Folders and Files:

Common Folder (/src/common)

  • DTOs (/src/common/dto/): Shared Data Transfer Objects for validation across multiple modules.
  • Guards (/src/common/guards/): Custom guards for protecting routes, such as JWT authentication or role-based access.
  • Filters (/src/common/filters/): Exception filters for catching and transforming exceptions across the app.
  • Utils (/src/common/utils/): Utility functions that are used across multiple modules, such as helpers for date formatting or pagination.

Config Folder (/src/config)

  • config.service.ts: Centralized configuration logic for handling environment variables using @nestjs/config. This ensures easy access to environment variables for different services like database connection strings or API keys.
  • config.module.ts: NestJS module to manage the application’s configuration.

Database Folder (/src/database)

  • Entities (/src/database/entities/): Stores TypeORM entities for modeling data structures such as User, Pet, and AdoptionApplication.
    • user.entity.ts: Defines the User entity with fields like id, name, email, and their relationships to pets or applications.
    • pet.entity.ts: Defines the Pet entity with attributes such as name, breed, age, location.
    • application.entity.ts: Defines the AdoptionApplication entity, linking users and pets with fields such as status, comments, and timestamps.
  • Migrations (/src/database/migrations/): Store migration files for database schema updates using TypeORM.

Modules Folder (/src/modules)

  • Auth Module (/src/modules/auth/): Manages authentication using JWT and login strategies.

    • auth.controller.ts: Handles routes for login, registration, and token refresh.
    • auth.service.ts: Implements the business logic for user authentication and JWT generation.
    • jwt.strategy.ts: Strategy for validating JWT tokens.
  • Users Module (/src/modules/users/): Manages user registration, profile management, and user CRUD.

    • users.controller.ts: Exposes API routes for user management.
    • users.service.ts: Contains business logic related to user operations (e.g., update profile, get user data).
  • Pets Module (/src/modules/pets/): Manages pet listing CRUD (create, read, update, delete).

    • pets.controller.ts: Provides routes for managing pet listings (e.g., /pets, /pets/:id).
    • pets.service.ts: Handles business logic for pet operations (e.g., adding, deleting, updating pets).
  • Adoption Module (/src/modules/adoption/): Handles adoption requests and application management.

    • adoption.controller.ts: Routes for managing adoption applications (e.g., submit, update status).
    • adoption.service.ts: Handles application processing logic.
  • Notifications Module (/src/modules/notifications/): Handles email notifications, push notifications, and SMS alerts.

    • email.service.ts: Contains logic for sending emails (e.g., using a service like SendGrid).
    • push.service.ts: Manages push notifications (e.g., via Firebase).
  • Admin Module (/src/modules/admin/): Manages admin-specific functionalities, such as reviewing adoption applications and managing users or pets.

Root Files

  • app.module.ts: The root module of the NestJS app that imports other feature modules.
  • main.ts: The entry point of the NestJS app, where the app is initialized and run.

Tests Folder (/test/)

  • Contains unit tests and integration tests for the core modules (e.g., Pets, Users).
    • pets.service.spec.ts: Unit tests for the pet service.
    • users.service.spec.ts

: Unit tests for the user service.


Example of Key Files

auth.service.ts

Handles user authentication and JWT token creation:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { LoginDto, RegisterDto } from './dto';

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

  async login(loginDto: LoginDto) {
    const user = await this.usersService.validateUser(
      loginDto.email,
      loginDto.password,
    );
    const payload = { username: user.username, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }

  async register(registerDto: RegisterDto) {
    const newUser = await this.usersService.create(registerDto);
    return this.login({ email: newUser.email, password: registerDto.password });
  }
}
Enter fullscreen mode Exit fullscreen mode

pets.controller.ts

Provides routes for fetching and managing pets:

import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { PetsService } from './pets.service';
import { CreatePetDto, UpdatePetDto } from './dto';

@Controller('pets')
export class PetsController {
  constructor(private readonly petsService: PetsService) {}

  @Get()
  findAll() {
    return this.petsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.petsService.findOne(id);
  }

  @Post()
  create(@Body() createPetDto: CreatePetDto) {
    return this.petsService.create(createPetDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updatePetDto: UpdatePetDto) {
    return this.petsService.update(id, updatePetDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.petsService.remove(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

pet.entity.ts

Defines the Pet entity with TypeORM:

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

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

  @Column()
  name: string;

  @Column()
  breed: string;

  @Column()
  age: number;

  @Column()
  location: string;

  @Column({ default: true })
  isAvailable: boolean;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

This modular folder structure is designed to handle core features like user authentication, pet management, adoption processes, and notifications. It provides a scalable framework to add more features in the future, while keeping the codebase clean and maintainable.

Here’s the complete code for the Adoption-related components and Pet-related components based on the structure you provided:

Folder Structure:

├── public/                    
│   ├── images/                
│   └── favicon.ico            
├── src/                       
│   ├── components/            
│   │   ├── Adoption/          
│   │   │   ├── AdoptionForm.tsx
│   │   │   ├── ApplicationStatus.tsx
│   │   │   ├── ApplicationList.tsx
│   │   └── Pet/               
│   │       ├── PetCard.tsx    
│   │       ├── PetDetails.tsx 
│   │       ├── PetFilters.tsx 
│   │       └── PetList.tsx    
Enter fullscreen mode Exit fullscreen mode

1. Adoption Components

1.1 AdoptionForm.tsx

This component handles the adoption application form.

import { useState } from 'react';

const AdoptionForm = ({ petId }: { petId: string }) => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    // Send data to the server
    await fetch('/api/adoptions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ ...formData, petId }),
    });
    setSubmitted(true);
  };

  return (
    <div className="max-w-lg mx-auto">
      <h2 className="text-2xl font-bold">Adopt {petId}</h2>
      {submitted ? (
        <p className="text-green-500">Your application has been submitted!</p>
      ) : (
        <form onSubmit={handleSubmit}>
          <div className="mb-4">
            <label htmlFor="name" className="block text-sm font-medium">Name</label>
            <input
              type="text"
              id="name"
              value={formData.name}
              onChange={(e) => setFormData({ ...formData, name: e.target.value })}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div className="mb-4">
            <label htmlFor="email" className="block text-sm font-medium">Email</label>
            <input
              type="email"
              id="email"
              value={formData.email}
              onChange={(e) => setFormData({ ...formData, email: e.target.value })}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <div className="mb-4">
            <label htmlFor="message" className="block text-sm font-medium">Why do you want to adopt this pet?</label>
            <textarea
              id="message"
              value={formData.message}
              onChange={(e) => setFormData({ ...formData, message: e.target.value })}
              className="mt-1 p-2 w-full border rounded-md"
              required
            />
          </div>
          <button type="submit" className="bg-blue-500 text-white p-2 rounded-md">
            Submit Application
          </button>
        </form>
      )}
    </div>
  );
};

export default AdoptionForm;
Enter fullscreen mode Exit fullscreen mode

1.2 ApplicationStatus.tsx

Displays the status of the adoption application.

const ApplicationStatus = ({ status }: { status: string }) => {
  return (
    <div className="border p-4 rounded-lg bg-gray-50">
      <h3 className="font-bold text-xl mb-2">Application Status</h3>
      <p className={`text-${status === 'Approved' ? 'green' : 'red'}-500`}>
        {status === 'Approved' ? 'Your application has been approved!' : 'Your application is still under review.'}
      </p>
    </div>
  );
};

export default ApplicationStatus;
Enter fullscreen mode Exit fullscreen mode

1.3 ApplicationList.tsx

Lists all the adoption applications for a user.

import { useEffect, useState } from 'react';

interface Application {
  id: string;
  petName: string;
  status: string;
}

const ApplicationList = () => {
  const [applications, setApplications] = useState<Application[]>([]);

  useEffect(() => {
    // Fetch user applications from the backend
    fetch('/api/adoptions')
      .then((response) => response.json())
      .then((data) => setApplications(data));
  }, []);

  return (
    <div className="max-w-3xl mx-auto">
      <h2 className="text-2xl font-bold mb-4">Your Adoption Applications</h2>
      {applications.length === 0 ? (
        <p>No applications found.</p>
      ) : (
        <ul>
          {applications.map((application) => (
            <li key={application.id} className="mb-4 border p-4 rounded-lg">
              <h3 className="font-bold">{application.petName}</h3>
              <p>Status: {application.status}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default ApplicationList;
Enter fullscreen mode Exit fullscreen mode

2. Pet Components

2.1 PetCard.tsx

Displays a pet card with basic information.

import Link from 'next/link';

interface PetCardProps {
  pet: {
    id: string;
    name: string;
    breed: string;
    age: number;
    imageUrl: string;
    location: string;
  };
}

const PetCard = ({ pet }: PetCardProps) => {
  return (
    <div className="border rounded-lg overflow-hidden shadow-lg">
      <img src={pet.imageUrl} alt={pet.name} className="w-full h-48 object-cover" />
      <div className="p-4">
        <h3 className="text-xl font-bold">{pet.name}</h3>
        <p>Breed: {pet.breed}</p>
        <p>Age: {pet.age} years</p>
        <p>Location: {pet.location}</p>
        <Link href={`/adoptable-pets/${pet.id}`}>
          <a className="text-blue-500 mt-2 inline-block">View Details</a>
        </Link>
      </div>
    </div>
  );
};

export default PetCard;
Enter fullscreen mode Exit fullscreen mode

2.2 PetDetails.tsx

Displays detailed information about a specific pet.

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import AdoptionForm from '../Adoption/AdoptionForm';

interface PetDetailsProps {
  id: string;
  name: string;
  breed: string;
  age: number;
  description: string;
  imageUrl: string;
  location: string;
}

const PetDetails = () => {
  const router = useRouter();
  const { id } = router.query;
  const [pet, setPet] = useState<PetDetailsProps | null>(null);

  useEffect(() => {
    if (id) {
      // Fetch pet details from the backend
      fetch(`/api/pets/${id}`)
        .then((response) => response.json())
        .then((data) => setPet(data));
    }
  }, [id]);

  if (!pet) return <p>Loading...</p>;

  return (
    <div className="max-w-4xl mx-auto mt-8">
      <img src={pet.imageUrl} alt={pet.name} className="w-full h-96 object-cover rounded-md" />
      <div className="mt-4">
        <h2 className="text-3xl font-bold">{pet.name}</h2>
        <p className="text-lg">Breed: {pet.breed}</p>
        <p className="text-lg">Age: {pet.age} years</p>
        <p className="mt-4">{pet.description}</p>
        <p className="mt-4">Location: {pet.location}</p>
        <AdoptionForm petId={pet.id} />
      </div>
    </div>
  );
};

export default PetDetails;
Enter fullscreen mode Exit fullscreen mode

2.3 PetFilters.tsx

A filter panel to filter pets by breed, age, and location.

import { useState } from 'react';

const PetFilters = ({ onFilter }: { onFilter: (filters: any) => void }) => {
  const [filters, setFilters] = useState({
    breed: '',
    location: '',
    age: '',
  });

  const handleFilterChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
    setFilters({
      ...filters,
      [e.target.name]: e.target.value,
    });
  };

  const handleApplyFilters = () => {
    onFilter(filters);
  };

  return (
    <div className="border p-4 rounded-lg bg-gray-50">
      <h3 className="font-bold mb-4">Filter Pets</h3>
      <div className="mb-4">
        <label htmlFor="breed"

 className="block text-sm font-medium">Breed</label>
        <input
          type="text"
          id="breed"
          name="breed"
          value={filters.breed}
          onChange={handleFilterChange}
          className="w-full border p-2 rounded-md"
        />
      </div>
      <div className="mb-4">
        <label htmlFor="location" className="block text-sm font-medium">Location</label>
        <input
          type="text"
          id="location"
          name="location"
          value={filters.location}
          onChange={handleFilterChange}
          className="w-full border p-2 rounded-md"
        />
      </div>
      <div className="mb-4">
        <label htmlFor="age" className="block text-sm font-medium">Age</label>
        <input
          type="number"
          id="age"
          name="age"
          value={filters.age}
          onChange={handleFilterChange}
          className="w-full border p-2 rounded-md"
        />
      </div>
      <button
        onClick={handleApplyFilters}
        className="bg-blue-500 text-white p-2 rounded-md w-full"
      >
        Apply Filters
      </button>
    </div>
  );
};

export default PetFilters;
Enter fullscreen mode Exit fullscreen mode

2.4 PetList.tsx

Displays a list of pets filtered by search or filter options.

import { useEffect, useState } from 'react';
import PetCard from './PetCard';

const PetList = () => {
  const [pets, setPets] = useState([]);
  const [filters, setFilters] = useState({});

  useEffect(() => {
    // Fetch the list of pets from the backend
    fetch('/api/pets')
      .then((response) => response.json())
      .then((data) => setPets(data));
  }, []);

  const handleFilter = (filterValues: any) => {
    setFilters(filterValues);
    // You can also re-fetch pets based on filters using an API call
  };

  return (
    <div>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
        {pets.map((pet) => (
          <PetCard key={pet.id} pet={pet} />
        ))}
      </div>
    </div>
  );
};

export default PetList;
Enter fullscreen mode Exit fullscreen mode

Conclusion:

This complete code for the AdoptionForm, ApplicationStatus, PetCard, PetDetails, PetFilters, and PetList components will handle the key functionalities like listing pets, viewing detailed pet information, filtering pets, and submitting adoption applications. You can integrate these with backend services to create a fully functional pet adoption platform.

Here’s the complete code for the custom hooks, layout components, and API routes based on your structure.

Folder Structure:

├── src/
│   ├── hooks/                 # Custom hooks
│   │   └── useFetch.ts        # Fetch data hook
│   ├── layout/                # Shared layout components
│   │   ├── Header.tsx         # Header (Navbar)
│   │   ├── Footer.tsx         # Footer component
│   │   └── Sidebar.tsx        # Admin/Sidebar menu
│   ├── pages/                 # Next.js pages (routing)
│   │   ├── api/               # API routes for server-side handling
│   │   │   ├── pets/          # API for pet operations
│   │   │   ├── users/         # API for user-related actions
Enter fullscreen mode Exit fullscreen mode

1. Custom Hooks

1.1 useFetch.ts

A simple reusable custom hook to handle data fetching.

import { useEffect, useState } from 'react';

const useFetch = (url: string) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (error: any) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;
Enter fullscreen mode Exit fullscreen mode

2. Layout Components

2.1 Header.tsx

A responsive header (navbar) component with links to different parts of the site.

import Link from 'next/link';

const Header = () => {
  return (
    <header className="bg-blue-500 p-4 text-white shadow-md">
      <div className="container mx-auto flex justify-between items-center">
        <h1 className="text-2xl font-bold">
          <Link href="/">Pet Adoption Platform</Link>
        </h1>
        <nav>
          <ul className="flex space-x-4">
            <li><Link href="/">Home</Link></li>
            <li><Link href="/adoptable-pets">Pets</Link></li>
            <li><Link href="/about">About Us</Link></li>
            <li><Link href="/account">Account</Link></li>
          </ul>
        </nav>
      </div>
    </header>
  );
};

export default Header;
Enter fullscreen mode Exit fullscreen mode

2.2 Footer.tsx

A simple footer component with some additional links.

const Footer = () => {
  return (
    <footer className="bg-gray-800 text-white p-4">
      <div className="container mx-auto text-center">
        <p>&copy; {new Date().getFullYear()} Pet Adoption Platform</p>
        <ul className="flex justify-center space-x-4 mt-2">
          <li><a href="/privacy">Privacy Policy</a></li>
          <li><a href="/terms">Terms of Service</a></li>
          <li><a href="/contact">Contact Us</a></li>
        </ul>
      </div>
    </footer>
  );
};

export default Footer;
Enter fullscreen mode Exit fullscreen mode

2.3 Sidebar.tsx

Admin sidebar menu for navigation within the admin dashboard.

import Link from 'next/link';

const Sidebar = () => {
  return (
    <aside className="bg-gray-100 w-64 p-4 shadow-md">
      <nav>
        <ul>
          <li className="mb-4"><Link href="/admin">Dashboard</Link></li>
          <li className="mb-4"><Link href="/admin/pets">Manage Pets</Link></li>
          <li className="mb-4"><Link href="/admin/applications">Adoption Applications</Link></li>
          <li className="mb-4"><Link href="/admin/users">Manage Users</Link></li>
        </ul>
      </nav>
    </aside>
  );
};

export default Sidebar;
Enter fullscreen mode Exit fullscreen mode

3. API Routes

3.1 /pages/api/pets/index.ts

API route to handle pet listing operations such as retrieving all pets or adding a new pet.

import { NextApiRequest, NextApiResponse } from 'next';

// Mock data for demo purposes
const pets = [
  { id: 1, name: 'Max', breed: 'Labrador', age: 3, location: 'New York' },
  { id: 2, name: 'Bella', breed: 'Poodle', age: 2, location: 'Los Angeles' },
  { id: 3, name: 'Charlie', breed: 'Beagle', age: 4, location: 'Chicago' },
];

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    // Retrieve list of all pets
    res.status(200).json(pets);
  } else if (req.method === 'POST') {
    // Add a new pet (for simplicity, we don't save this anywhere)
    const newPet = req.body;
    pets.push(newPet);
    res.status(201).json(newPet);
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}
Enter fullscreen mode Exit fullscreen mode

3.2 /pages/api/pets/[id].ts

API route to handle operations for a specific pet (e.g., retrieving details, updating, deleting).

import { NextApiRequest, NextApiResponse } from 'next';

// Mock data for demo purposes
const pets = [
  { id: 1, name: 'Max', breed: 'Labrador', age: 3, location: 'New York' },
  { id: 2, name: 'Bella', breed: 'Poodle', age: 2, location: 'Los Angeles' },
  { id: 3, name: 'Charlie', breed: 'Beagle', age: 4, location: 'Chicago' },
];

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query;
  const pet = pets.find((p) => p.id === Number(id));

  if (!pet) {
    res.status(404).json({ message: 'Pet not found' });
    return;
  }

  if (req.method === 'GET') {
    // Return specific pet details
    res.status(200).json(pet);
  } else if (req.method === 'PUT') {
    // Update pet details
    const updatedPet = { ...pet, ...req.body };
    pets[pets.findIndex((p) => p.id === Number(id))] = updatedPet;
    res.status(200).json(updatedPet);
  } else if (req.method === 'DELETE') {
    // Delete pet
    const index = pets.findIndex((p) => p.id === Number(id));
    pets.splice(index, 1);
    res.status(204).end();
  } else {
    res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}
Enter fullscreen mode Exit fullscreen mode

3.3 /pages/api/users/index.ts

API route to handle user operations such as retrieving user information or registering a new user.

import { NextApiRequest, NextApiResponse } from 'next';

// Mock user data
const users = [
  { id: 1, name: 'John Doe', email: 'john@example.com', role: 'user' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'admin' },
];

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    // Retrieve all users
    res.status(200).json(users);
  } else if (req.method === 'POST') {
    // Register a new user (for simplicity, we don't persist the user)
    const newUser = req.body;
    users.push(newUser);
    res.status(201).json(newUser);
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary:

  • Custom Hook: useFetch.ts handles data fetching for any API.
  • Layout Components:
    • Header.tsx provides navigation across the site.
    • Footer.tsx provides a footer with essential links.
    • Sidebar.tsx is a simple admin sidebar menu.
  • API Routes:
    • /api/pets/index.ts provides API functionality for listing pets and adding new ones.
    • /api/pets/[id].ts handles specific pet operations like retrieving, updating, and deleting pets.
    • /api/users/index.ts manages user operations such as retrieving or adding new

users.

These components and APIs together form the base structure for managing pets, users, and general UI elements for the platform.

Here is the full code for the dynamic pet detail page, user account management, and admin dashboard based on the structure you provided.

Folder Structure:

├── src/
│   ├── pages/
│   │   ├── adoptable-pets/    # Dynamic routes for pets
│   │   │   └── [id].tsx       # Individual pet detail page
│   │   ├── account/           # User account management
│   │   │   ├── profile.tsx    # User profile management page
│   │   │   └── applications.tsx  # View user's submitted adoption applications
│   │   ├── admin/             # Admin dashboard
│   │   │   ├── index.tsx      # Admin dashboard main page
│   │   │   ├── pets.tsx       # CRUD operations for pets
│   │   │   ├── applications.tsx # Manage adoption applications
Enter fullscreen mode Exit fullscreen mode

1. Dynamic Pet Detail Page

/adoptable-pets/[id].tsx

This page dynamically displays details of an individual pet based on its ID.

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import AdoptionForm from '../../components/Adoption/AdoptionForm';

const PetDetails = () => {
  const router = useRouter();
  const { id } = router.query; // Dynamic route parameter
  const [pet, setPet] = useState<any>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (id) {
      // Fetch the pet details by ID
      fetch(`/api/pets/${id}`)
        .then((res) => res.json())
        .then((data) => {
          setPet(data);
          setLoading(false);
        });
    }
  }, [id]);

  if (loading) return <p>Loading...</p>;
  if (!pet) return <p>Pet not found</p>;

  return (
    <div className="container mx-auto p-8">
      <div className="flex flex-col items-center">
        <img src={pet.imageUrl} alt={pet.name} className="w-1/2 rounded-md shadow-lg" />
        <h2 className="text-3xl font-bold mt-4">{pet.name}</h2>
        <p className="text-xl">Breed: {pet.breed}</p>
        <p className="text-xl">Age: {pet.age} years</p>
        <p className="text-xl">Location: {pet.location}</p>
        <p className="text-md mt-4">{pet.description}</p>

        {/* Adoption Form */}
        <div className="mt-6 w-full md:w-1/2">
          <AdoptionForm petId={id as string} />
        </div>
      </div>
    </div>
  );
};

export default PetDetails;
Enter fullscreen mode Exit fullscreen mode

2. User Account Management

2.1 /account/profile.tsx

A page where users can manage their account information.

import { useState, useEffect } from 'react';

const Profile = () => {
  const [profileData, setProfileData] = useState({
    name: '',
    email: '',
  });
  const [loading, setLoading] = useState(true);
  const [message, setMessage] = useState('');

  useEffect(() => {
    // Fetch user profile data from the backend
    fetch('/api/users/me')
      .then((res) => res.json())
      .then((data) => {
        setProfileData(data);
        setLoading(false);
      });
  }, []);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setProfileData({
      ...profileData,
      [e.target.name]: e.target.value,
    });
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    // Update user profile data
    const response = await fetch('/api/users/me', {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(profileData),
    });
    const result = await response.json();
    setMessage(result.message || 'Profile updated successfully.');
  };

  if (loading) return <p>Loading profile...</p>;

  return (
    <div className="container mx-auto p-8">
      <h2 className="text-2xl font-bold mb-4">Your Profile</h2>
      {message && <p className="text-green-500 mb-4">{message}</p>}
      <form onSubmit={handleSubmit}>
        <div className="mb-4">
          <label htmlFor="name" className="block text-sm font-medium">Name</label>
          <input
            type="text"
            id="name"
            name="name"
            value={profileData.name}
            onChange={handleChange}
            className="w-full p-2 border rounded-md"
          />
        </div>
        <div className="mb-4">
          <label htmlFor="email" className="block text-sm font-medium">Email</label>
          <input
            type="email"
            id="email"
            name="email"
            value={profileData.email}
            onChange={handleChange}
            className="w-full p-2 border rounded-md"
          />
        </div>
        <button
          type="submit"
          className="bg-blue-500 text-white p-2 rounded-md"
        >
          Save Changes
        </button>
      </form>
    </div>
  );
};

export default Profile;
Enter fullscreen mode Exit fullscreen mode

2.2 /account/applications.tsx

A page where users can view their submitted adoption applications.

import { useEffect, useState } from 'react';

interface Application {
  id: string;
  petName: string;
  status: string;
}

const Applications = () => {
  const [applications, setApplications] = useState<Application[]>([]);

  useEffect(() => {
    // Fetch the user's adoption applications
    fetch('/api/adoptions/me')
      .then((res) => res.json())
      .then((data) => setApplications(data));
  }, []);

  return (
    <div className="container mx-auto p-8">
      <h2 className="text-2xl font-bold mb-4">Your Adoption Applications</h2>
      {applications.length === 0 ? (
        <p>No applications found.</p>
      ) : (
        <ul>
          {applications.map((application) => (
            <li key={application.id} className="mb-4 p-4 border rounded-lg">
              <h3 className="font-bold">Pet: {application.petName}</h3>
              <p>Status: {application.status}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default Applications;
Enter fullscreen mode Exit fullscreen mode

3. Admin Dashboard

3.1 /admin/index.tsx

Admin dashboard main page, providing an overview of the platform.

import Link from 'next/link';

const AdminDashboard = () => {
  return (
    <div className="container mx-auto p-8">
      <h2 className="text-3xl font-bold mb-6">Admin Dashboard</h2>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
        <Link href="/admin/pets">
          <a className="bg-blue-500 text-white p-4 rounded-md shadow-lg text-center">
            Manage Pets
          </a>
        </Link>
        <Link href="/admin/applications">
          <a className="bg-blue-500 text-white p-4 rounded-md shadow-lg text-center">
            Manage Applications
          </a>
        </Link>
        <Link href="/admin/users">
          <a className="bg-blue-500 text-white p-4 rounded-md shadow-lg text-center">
            Manage Users
          </a>
        </Link>
      </div>
    </div>
  );
};

export default AdminDashboard;
Enter fullscreen mode Exit fullscreen mode

3.2 /admin/pets.tsx

A page where admins can perform CRUD operations for pets.

import { useEffect, useState } from 'react';

interface Pet {
  id: string;
  name: string;
  breed: string;
  age: number;
  location: string;
}

const AdminPets = () => {
  const [pets, setPets] = useState<Pet[]>([]);

  useEffect(() => {
    // Fetch the list of pets
    fetch('/api/pets')
      .then((res) => res.json())
      .then((data) => setPets(data));
  }, []);

  const handleDelete = async (id: string) => {
    await fetch(`/api/pets/${id}`, { method: 'DELETE' });
    setPets(pets.filter((pet) => pet.id !== id));
  };

  return (
    <div className="container mx-auto p-8">
      <h2 className="text-2xl font-bold mb-4">Manage Pets</h2>
      <ul>
        {pets.map((pet) => (
          <li key={pet.id} className="mb-4 p-4 border rounded-lg">
            <h3

 className="font-bold">{pet.name}</h3>
            <p>Breed: {pet.breed}</p>
            <p>Age: {pet.age} years</p>
            <p>Location: {pet.location}</p>
            <button
              onClick={() => handleDelete(pet.id)}
              className="bg-red-500 text-white p-2 rounded-md mt-2"
            >
              Delete Pet
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default AdminPets;
Enter fullscreen mode Exit fullscreen mode

3.3 /admin/applications.tsx

A page where admins can manage adoption applications.

import { useEffect, useState } from 'react';

interface Application {
  id: string;
  petName: string;
  applicantName: string;
  status: string;
}

const AdminApplications = () => {
  const [applications, setApplications] = useState<Application[]>([]);

  useEffect(() => {
    // Fetch all adoption applications
    fetch('/api/adoptions')
      .then((res) => res.json())
      .then((data) => setApplications(data));
  }, []);

  const handleStatusChange = async (id: string, status: string) => {
    await fetch(`/api/adoptions/${id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ status }),
    });
    setApplications(
      applications.map((app) =>
        app.id === id ? { ...app, status } : app
      )
    );
  };

  return (
    <div className="container mx-auto p-8">
      <h2 className="text-2xl font-bold mb-4">Manage Applications</h2>
      <ul>
        {applications.map((application) => (
          <li key={application.id} className="mb-4 p-4 border rounded-lg">
            <h3 className="font-bold">Pet: {application.petName}</h3>
            <p>Applicant: {application.applicantName}</p>
            <p>Status: {application.status}</p>
            <div className="mt-2">
              <button
                onClick={() => handleStatusChange(application.id, 'Approved')}
                className="bg-green-500 text-white p-2 rounded-md mr-2"
              >
                Approve
              </button>
              <button
                onClick={() => handleStatusChange(application.id, 'Rejected')}
                className="bg-red-500 text-white p-2 rounded-md"
              >
                Reject
              </button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default AdminApplications;
Enter fullscreen mode Exit fullscreen mode

Conclusion:

This code includes the following pages:

  • Pet detail page: Displays individual pet details along with an adoption form.
  • User account management:
    • Profile page: Allows users to update their profile.
    • Adoption applications: Lists a user's submitted applications.
  • Admin dashboard:
    • Manage pets: Allows admins to view and delete pets.
    • Manage applications: Allows admins to review, approve, or reject adoption applications.

These components, when integrated with appropriate backend APIs, form the foundation of the platform’s user and admin experience.

Here’s the complete code for the home page, adoption form page, favorites page, about page, as well as the services for API calls, styles, utility functions, and configuration settings based on your provided structure.


Folder Structure:

├── src/
│   ├── pages/
│   │   └── index.tsx          # Home page
│   │   └── adopt.tsx          # Adoption page (form submission)
│   │   └── favorites.tsx      # User's favorite pets (wishlist)
│   │   └── about.tsx          # Static About Us page
│   ├── services/              # Services for API calls
│   │   ├── petService.ts      # API calls for fetching pet data
│   │   ├── userService.ts     # User authentication and profile management services
│   │   └── applicationService.ts # Services for handling adoption applications
│   ├── styles/                # Global styles
│   │   ├── globals.css        # Global Tailwind CSS styles
│   │   └── tailwind.css       # Custom Tailwind styles (if needed)
│   ├── utils/                 # Utility functions
│   │   ├── helpers.ts         # General helper functions (e.g., formatting)
│   │   └── validators.ts      # Form validation helpers
│   └── config/                # Configuration settings
│       └── api.ts             # API base URL and config settings
Enter fullscreen mode Exit fullscreen mode

1. Pages

1.1 index.tsx (Home Page)

Displays the homepage with a list of adoptable pets.

import PetList from '../components/Pet/PetList';

const HomePage = () => {
  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Welcome to the Pet Adoption Platform</h1>
      <p className="mb-4">
        Find your perfect pet companion by browsing through our adoptable pets.
      </p>
      <PetList />
    </div>
  );
};

export default HomePage;
Enter fullscreen mode Exit fullscreen mode

1.2 adopt.tsx (Adoption Page)

This page contains the form where users can submit an adoption application.

import { useRouter } from 'next/router';
import AdoptionForm from '../components/Adoption/AdoptionForm';

const AdoptionPage = () => {
  const router = useRouter();
  const { petId } = router.query;

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-2xl font-bold mb-4">Adopt Your New Best Friend</h1>
      {petId ? (
        <AdoptionForm petId={petId as string} />
      ) : (
        <p>Select a pet to adopt from our listings.</p>
      )}
    </div>
  );
};

export default AdoptionPage;
Enter fullscreen mode Exit fullscreen mode

1.3 favorites.tsx (Favorites/Wishlist Page)

Displays the user's favorite pets.

import { useState, useEffect } from 'react';
import PetCard from '../components/Pet/PetCard';

const FavoritesPage = () => {
  const [favorites, setFavorites] = useState([]);

  useEffect(() => {
    // Fetch user's favorite pets
    fetch('/api/users/favorites')
      .then((res) => res.json())
      .then((data) => setFavorites(data));
  }, []);

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-2xl font-bold mb-4">Your Favorite Pets</h1>
      {favorites.length === 0 ? (
        <p>No favorite pets added yet.</p>
      ) : (
        <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
          {favorites.map((pet) => (
            <PetCard key={pet.id} pet={pet} />
          ))}
        </div>
      )}
    </div>
  );
};

export default FavoritesPage;
Enter fullscreen mode Exit fullscreen mode

1.4 about.tsx (About Page)

Static page with information about the platform.

const AboutPage = () => {
  return (
    <div className="container mx-auto p-8">
      <h1 className="text-2xl font-bold mb-4">About Us</h1>
      <p>
        Welcome to our Pet Adoption Platform, where we help find loving homes
        for pets in need. Our mission is to connect pets with people looking to
        adopt, fostering a community of pet lovers.
      </p>
      <p className="mt-4">
        We work with multiple shelters and rescues to ensure every pet finds a
        home. Join us in our journey to help pets find their forever homes!
      </p>
    </div>
  );
};

export default AboutPage;
Enter fullscreen mode Exit fullscreen mode

2. Services

2.1 petService.ts

Handles API calls for fetching pet data.

export const fetchPets = async () => {
  const response = await fetch('/api/pets');
  if (!response.ok) {
    throw new Error('Failed to fetch pets');
  }
  return response.json();
};

export const fetchPetById = async (id: string) => {
  const response = await fetch(`/api/pets/${id}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch pet with id: ${id}`);
  }
  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

2.2 userService.ts

Handles user authentication and profile management.

export const fetchUserProfile = async () => {
  const response = await fetch('/api/users/me');
  if (!response.ok) {
    throw new Error('Failed to fetch user profile');
  }
  return response.json();
};

export const updateUserProfile = async (data: any) => {
  const response = await fetch('/api/users/me', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  });
  if (!response.ok) {
    throw new Error('Failed to update profile');
  }
  return response.json();
};

export const fetchUserFavorites = async () => {
  const response = await fetch('/api/users/favorites');
  if (!response.ok) {
    throw new Error('Failed to fetch user favorites');
  }
  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

2.3 applicationService.ts

Handles the adoption application process.

export const submitAdoptionApplication = async (data: any) => {
  const response = await fetch('/api/adoptions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  });
  if (!response.ok) {
    throw new Error('Failed to submit adoption application');
  }
  return response.json();
};

export const fetchUserApplications = async () => {
  const response = await fetch('/api/adoptions/me');
  if (!response.ok) {
    throw new Error('Failed to fetch adoption applications');
  }
  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

3. Styles

3.1 globals.css

Global styles for the application, including base Tailwind styles.

@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom global styles can be added here */
body {
  font-family: 'Inter', sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

3.2 tailwind.css

Optional custom Tailwind CSS styles or configurations (if needed).

@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom Tailwind components or variants */
Enter fullscreen mode Exit fullscreen mode

4. Utility Functions

4.1 helpers.ts

General utility functions, such as date formatting.

export const formatDate = (date: string) => {
  const options: Intl.DateTimeFormatOptions = {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  };
  return new Date(date).toLocaleDateString(undefined, options);
};
Enter fullscreen mode Exit fullscreen mode

4.2 validators.ts

Form validation helpers for validating user inputs.

export const validateEmail = (email: string) => {
  const re = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
  return re.test(email);
};

export const validateRequired = (value: string) => {
  return value.trim() !== '';
};
Enter fullscreen mode Exit fullscreen mode

5. Configuration

api.ts

Contains the base URL and other API configuration settings.

const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000';

export const getApiUrl = (path: string) => `${apiBaseUrl}${path}`;
Enter fullscreen mode Exit fullscreen mode

Conclusion:

  • Pages: The code includes a homepage, adoption form page, favorites page, and an about page.
  • Services: There are service functions for handling API calls related to pets, users, and adoption applications.
  • Styles: Global styles and Tailwind CSS configurations.
  • Utilities: Helper functions for general purposes and

form validation.

  • Configuration: Basic API configuration to handle base URLs dynamically.

This setup provides a solid structure for your Pet Adoption Platform using Next.js, Tailwind CSS, and a well-organized service layer.

Here is the full code for the backend folder structure you provided, including common resources, configuration files, and database setup using NestJS and TypeORM.

Backend Folder Structure

├── src/
│   ├── common/                 # Shared resources like guards, filters, DTOs, utilities
│   │   ├── dto/                # Shared DTOs
│   │   ├── filters/            # Exception filters
│   │   ├── guards/             # Authorization guards (e.g., roles)
│   │   └── utils/              # Utility functions used across modules
│   ├── config/                 # Configuration files (e.g., environment variables)
│   │   ├── config.module.ts    # Config service for environment variables
│   │   ├── config.service.ts   # Configuration management logic
│   └── database/               # Database-related files (TypeORM, migrations, etc.)
│       ├── entities/           # Database entities for TypeORM
│       │   ├── pet.entity.ts   # Pet entity
│       │   ├── user.entity.ts  # User entity
│       │   ├── application.entity.ts # Adoption application entity
│       ├── migrations/         # TypeORM migration files
│       └── database.module.ts  # Database connection configuration
Enter fullscreen mode Exit fullscreen mode

1. Common Resources

1.1 dto/ (Data Transfer Objects)

  • create-pet.dto.ts: DTO for creating a pet.
export class CreatePetDto {
  readonly name: string;
  readonly breed: string;
  readonly age: number;
  readonly location: string;
}
Enter fullscreen mode Exit fullscreen mode
  • create-user.dto.ts: DTO for user creation and registration.
export class CreateUserDto {
  readonly email: string;
  readonly password: string;
  readonly name: string;
}
Enter fullscreen mode Exit fullscreen mode
  • create-application.dto.ts: DTO for adoption applications.
export class CreateApplicationDto {
  readonly petId: number;
  readonly userId: number;
  readonly message: string;
}
Enter fullscreen mode Exit fullscreen mode

1.2 filters/ (Exception Filters)

  • http-exception.filter.ts: A global exception filter to handle HTTP errors.
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

1.3 guards/ (Authorization Guards)

  • roles.guard.ts: A role-based guard to protect routes based on user roles.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../user/user.entity'; // Assume roles are defined in the User entity

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

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

1.4 utils/ (Utility Functions)

  • hash.util.ts: Utility to hash passwords (e.g., using bcrypt).
import * as bcrypt from 'bcrypt';

export const hashPassword = async (password: string): Promise<string> => {
  const salt = await bcrypt.genSalt();
  return await bcrypt.hash(password, salt);
};

export const comparePassword = async (password: string, hash: string): Promise<boolean> => {
  return await bcrypt.compare(password, hash);
};
Enter fullscreen mode Exit fullscreen mode

2. Configuration Files

2.1 config.module.ts

This module provides configuration services (e.g., for reading environment variables).

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env'],
    }),
  ],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class AppConfigModule {}
Enter fullscreen mode Exit fullscreen mode

2.2 config.service.ts

This service manages the application's configuration settings (e.g., database connections, API keys).

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppConfigService {
  constructor(private configService: ConfigService) {}

  get databaseHost(): string {
    return this.configService.get<string>('DATABASE_HOST');
  }

  get databasePort(): number {
    return +this.configService.get<string>('DATABASE_PORT');
  }

  get databaseUser(): string {
    return this.configService.get<string>('DATABASE_USER');
  }

  get databasePassword(): string {
    return this.configService.get<string>('DATABASE_PASSWORD');
  }

  get databaseName(): string {
    return this.configService.get<string>('DATABASE_NAME');
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Database Configuration and Entities

3.1 pet.entity.ts (Pet Entity)

Defines the schema for the Pet table.

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

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

  @Column()
  name: string;

  @Column()
  breed: string;

  @Column()
  age: number;

  @Column()
  location: string;

  @Column({ default: true })
  isAvailable: boolean;
}
Enter fullscreen mode Exit fullscreen mode

3.2 user.entity.ts (User Entity)

Defines the schema for the User table, including authentication fields.

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

export enum Role {
  User = 'user',
  Admin = 'admin',
}

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

  @Column()
  name: string;

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

  @Column()
  password: string;

  @Column({ type: 'enum', enum: Role, default: Role.User })
  role: Role;
}
Enter fullscreen mode Exit fullscreen mode

3.3 application.entity.ts (Adoption Application Entity)

Defines the schema for the Application table.

import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Pet } from './pet.entity';
import { User } from './user.entity';

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

  @ManyToOne(() => Pet)
  pet: Pet;

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

  @Column()
  message: string;

  @Column({ default: 'pending' })
  status: string;
}
Enter fullscreen mode Exit fullscreen mode

4. Migrations

  • TypeORM Migration Example

You can generate migration files using the TypeORM CLI. Here's an example of how a migration file might look to create the pets, users, and applications tables.

import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateInitialTables1620487951231 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE "user" (
        "id" SERIAL NOT NULL,
        "name" VARCHAR NOT NULL,
        "email" VARCHAR NOT NULL UNIQUE,
        "password" VARCHAR NOT NULL,
        "role" VARCHAR NOT NULL DEFAULT 'user',
        PRIMARY KEY ("id")
      )
    `);

    await queryRunner.query(`
      CREATE TABLE "pet" (
        "id" SERIAL NOT NULL,
        "name" VARCHAR NOT NULL,
        "breed" VARCHAR NOT NULL,
        "age" INT NOT NULL,
        "location" VARCHAR NOT NULL,
        "isAvailable" BOOLEAN NOT NULL DEFAULT TRUE,
        PRIMARY KEY ("id")
      )
    `);

    await queryRunner.query(`
      CREATE TABLE "application" (
        "id" SERIAL NOT NULL,
        "message" VARCHAR NOT NULL,
        "status" VARCHAR NOT NULL DEFAULT 'pending',
        "userId" INT,
        "petId" INT,
        PRIMARY KEY ("id"),
        CONSTRAINT "FK_user_application" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE,
        CONSTRAINT "FK_pet_application" FOREIGN KEY ("petId") REFERENCES "pet"("id") ON DELETE CASCADE
      )
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE "application"`);
    await queryRunner.query(`DROP TABLE "pet"`);
    await queryRunner.query(`DROP TABLE "user"`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Running Migrations

Use the following TypeORM CLI commands to generate and run migrations

:

# Generate a migration
typeorm migration:generate -n CreateInitialTables

# Run migrations
typeorm migration:run
Enter fullscreen mode Exit fullscreen mode

5. Database Module

database.module.ts

This module configures the TypeORM connection and imports the entities.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Pet } from './entities/pet.entity';
import { User } from './entities/user.entity';
import { Application } from './entities/application.entity';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get<string>('DATABASE_HOST'),
        port: +configService.get<number>('DATABASE_PORT'),
        username: configService.get<string>('DATABASE_USER'),
        password: configService.get<string>('DATABASE_PASSWORD'),
        database: configService.get<string>('DATABASE_NAME'),
        entities: [Pet, User, Application],
        synchronize: true, // Set to `false` in production
      }),
      inject: [ConfigService],
    }),
    TypeOrmModule.forFeature([Pet, User, Application]),
  ],
  exports: [TypeOrmModule],
})
export class DatabaseModule {}
Enter fullscreen mode Exit fullscreen mode

Conclusion:

This backend folder structure and code provides a comprehensive setup using NestJS, TypeORM, and PostgreSQL. It includes:

  • Common resources: DTOs, filters, guards, and utilities.
  • Configuration: Managing environment variables and application settings.
  • Database entities: Entities for Pet, User, and Application tables.
  • Migrations: A TypeORM migration example.
  • Database module: Configures TypeORM and PostgreSQL integration.

You can now build a scalable and maintainable backend for your Pet Adoption Platform with this setup.

Here's the full code for the authentication module and user management module that you requested, including the JWT authentication, user management logic, DTOs, and controller files.

Backend Folder Structure

├── src/
│   ├── modules/
│   │   ├── auth/               # Authentication module (JWT)
│   │   │   ├── auth.controller.ts  # Auth-related routes (login, register)
│   │   │   ├── auth.module.ts      # Auth module declaration
│   │   │   ├── auth.service.ts     # Authentication logic
│   │   │   ├── jwt.strategy.ts     # JWT Strategy for securing routes
│   │   │   ├── local.strategy.ts   # Local strategy (login with email/password)
│   │   │   └── dto/                # DTOs for login, registration, etc.
│   │   ├── users/              # User management module
│   │   │   ├── users.controller.ts # User-related routes
│   │   │   ├── users.module.ts     # User module
│   │   │   ├── users.service.ts    # User management logic (CRUD, profile updates)
│   │   │   ├── user.entity.ts      # User entity (shared with database/entities)
│   │   │   └── dto/                # User-specific DTOs (Data Transfer Objects)
Enter fullscreen mode Exit fullscreen mode

1. Authentication Module (auth/)

1.1 auth.controller.ts

Handles authentication routes for login and registration.

import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './local.strategy';
import { CreateUserDto } from '../users/dto/create-user.dto';

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

  @Post('register')
  async register(@Body() createUserDto: CreateUserDto) {
    return this.authService.register(createUserDto);
  }

  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Request() req) {
    return this.authService.login(req.user);
  }
}
Enter fullscreen mode Exit fullscreen mode

1.2 auth.module.ts

Declares the Auth Module and imports necessary services like UserService, PassportModule, and JwtModule.

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

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

1.3 auth.service.ts

Handles the business logic for registering and logging in users. Also, it generates JWT tokens.

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';
import { CreateUserDto } from '../users/dto/create-user.dto';
import { hashPassword, comparePassword } from '../../common/utils/hash.util';

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

  async validateUser(email: string, pass: string): Promise<any> {
    const user = await this.usersService.findByEmail(email);
    if (user && await comparePassword(pass, user.password)) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

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

  async register(createUserDto: CreateUserDto) {
    const hashedPassword = await hashPassword(createUserDto.password);
    return this.usersService.create({
      ...createUserDto,
      password: hashedPassword,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

1.4 jwt.strategy.ts

JWT Strategy that extracts the JWT token from the request and validates it.

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

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private usersService: UsersService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET || 'secretKey',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, email: payload.username };
  }
}
Enter fullscreen mode Exit fullscreen mode

1.5 local.strategy.ts

Local strategy to authenticate users using email and password.

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({ usernameField: 'email' });
  }

  async validate(email: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(email, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

1.6 dto/ (Data Transfer Objects for Authentication)

  • login.dto.ts
export class LoginDto {
  readonly email: string;
  readonly password: string;
}
Enter fullscreen mode Exit fullscreen mode

2. User Management Module (users/)

2.1 users.controller.ts

Handles user-related routes like fetching user data, updating profile, etc.

import { Controller, Get, Post, Put, Body, Param, UseGuards, Request } from '@nestjs/common';
import { UsersService } from './users.service';
import { JwtAuthGuard } from '../auth/jwt.strategy';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UsersController {
  constructor(private usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @UseGuards(JwtAuthGuard)
  @Get('me')
  getProfile(@Request() req) {
    return this.usersService.findById(req.user.userId);
  }

  @UseGuards(JwtAuthGuard)
  @Put('me')
  updateProfile(@Request() req, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(req.user.userId, updateUserDto);
  }
}
Enter fullscreen mode Exit fullscreen mode

2.2 users.module.ts

Declares the User Module and imports the necessary services.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';

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

2.3 users.service.ts

Handles the business logic for user management like creating, updating, and finding users.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

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

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return this.usersRepository.save(user);
  }

  async findById(id: number): Promise<User> {
    return this.usersRepository.findOne(id);
  }

  async findByEmail(email: string): Promise<User> {
    return this.usersRepository.findOne({ where: { email } });
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    await this.usersRepository.update(id, updateUserDto);
    return this.findById(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

2.4 user.entity.ts

Defines the User Entity.

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Role } from './role.enum';

@Entity()


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

  @Column()
  name: string;

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

  @Column()
  password: string;

  @Column({ type: 'enum', enum: Role, default: Role.User })
  role: Role;
}
Enter fullscreen mode Exit fullscreen mode

2.5 dto/ (Data Transfer Objects for User Management)

  • create-user.dto.ts
export class CreateUserDto {
  readonly name: string;
  readonly email: string;
  readonly password: string;
}
Enter fullscreen mode Exit fullscreen mode
  • update-user.dto.ts
export class UpdateUserDto {
  readonly name?: string;
  readonly email?: string;
  readonly password?: string;
}
Enter fullscreen mode Exit fullscreen mode

Summary

  • Authentication Module (auth/): Contains logic for user registration, login, JWT token handling, and guards for protecting routes. The module includes:

    • auth.controller.ts: Routes for login and registration.
    • auth.service.ts: Business logic for validating users and issuing JWT tokens.
    • jwt.strategy.ts: Secures routes using JWT.
    • local.strategy.ts: Handles login using email and password.
    • DTOs: Defines the data structure for login and registration.
  • User Management Module (users/): Handles CRUD operations for users, including creating users, fetching user profiles, and updating user data. The module includes:

    • users.controller.ts: Routes for creating and managing user data.
    • users.service.ts: Business logic for user management.
    • user.entity.ts: Defines the database schema for users.
    • DTOs: Defines the data structure for creating and updating users.

This structure is highly modular and follows best practices for NestJS and TypeORM, making it easier to extend and maintain.

Here's the full code for the Pets management module and Adoption management module, including the controllers, services, entities, and DTOs for validation.

Backend Folder Structure

├── src/
│   ├── modules/
│   │   ├── pets/               # Pets management module
│   │   │   ├── pets.controller.ts  # Routes for handling pet listings
│   │   │   ├── pets.module.ts      # Pet module declaration
│   │   │   ├── pets.service.ts     # Business logic for pets
│   │   │   ├── pet.entity.ts       # Pet entity (shared with database/entities)
│   │   │   └── dto/                # Pet-related DTOs (for validation)
│   │   ├── adoption/           # Adoption management module
│   │   │   ├── adoption.controller.ts  # Routes for submitting and managing applications
│   │   │   ├── adoption.module.ts      # Adoption module declaration
│   │   │   ├── adoption.service.ts     # Business logic for adoption processes
│   │   │   ├── application.entity.ts   # Application entity (shared with database/entities)
│   │   │   └── dto/                    # Adoption-related DTOs
Enter fullscreen mode Exit fullscreen mode

1. Pets Management Module (pets/)

1.1 pets.controller.ts

Handles pet-related routes for listing, creating, updating, and deleting pets.

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { PetsService } from './pets.service';
import { CreatePetDto } from './dto/create-pet.dto';
import { UpdatePetDto } from './dto/update-pet.dto';

@Controller('pets')
export class PetsController {
  constructor(private readonly petsService: PetsService) {}

  @Get()
  findAll() {
    return this.petsService.findAll();
  }

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

  @Post()
  create(@Body() createPetDto: CreatePetDto) {
    return this.petsService.create(createPetDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updatePetDto: UpdatePetDto) {
    return this.petsService.update(+id, updatePetDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.petsService.remove(+id);
  }
}
Enter fullscreen mode Exit fullscreen mode

1.2 pets.module.ts

Declares the Pets Module and imports the necessary services.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PetsService } from './pets.service';
import { PetsController } from './pets.controller';
import { Pet } from './pet.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Pet])],
  controllers: [PetsController],
  providers: [PetsService],
})
export class PetsModule {}
Enter fullscreen mode Exit fullscreen mode

1.3 pets.service.ts

Handles the business logic for managing pets.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Pet } from './pet.entity';
import { CreatePetDto } from './dto/create-pet.dto';
import { UpdatePetDto } from './dto/update-pet.dto';

@Injectable()
export class PetsService {
  constructor(
    @InjectRepository(Pet)
    private petsRepository: Repository<Pet>,
  ) {}

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

  findOne(id: number): Promise<Pet> {
    return this.petsRepository.findOne(id);
  }

  create(createPetDto: CreatePetDto): Promise<Pet> {
    const pet = this.petsRepository.create(createPetDto);
    return this.petsRepository.save(pet);
  }

  async update(id: number, updatePetDto: UpdatePetDto): Promise<Pet> {
    await this.petsRepository.update(id, updatePetDto);
    return this.findOne(id);
  }

  async remove(id: number): Promise<void> {
    await this.petsRepository.delete(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

1.4 pet.entity.ts

Defines the Pet Entity, representing the structure of the pets in the database.

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

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

  @Column()
  name: string;

  @Column()
  breed: string;

  @Column()
  age: number;

  @Column()
  location: string;

  @Column({ default: true })
  isAvailable: boolean;
}
Enter fullscreen mode Exit fullscreen mode

1.5 dto/ (Pet-Related DTOs)

  • create-pet.dto.ts
export class CreatePetDto {
  readonly name: string;
  readonly breed: string;
  readonly age: number;
  readonly location: string;
}
Enter fullscreen mode Exit fullscreen mode
  • update-pet.dto.ts
export class UpdatePetDto {
  readonly name?: string;
  readonly breed?: string;
  readonly age?: number;
  readonly location?: string;
  readonly isAvailable?: boolean;
}
Enter fullscreen mode Exit fullscreen mode

2. Adoption Management Module (adoption/)

2.1 adoption.controller.ts

Handles routes for submitting and managing adoption applications.

import { Controller, Get, Post, Put, Body, Param } from '@nestjs/common';
import { AdoptionService } from './adoption.service';
import { CreateApplicationDto } from './dto/create-application.dto';
import { UpdateApplicationDto } from './dto/update-application.dto';

@Controller('adoptions')
export class AdoptionController {
  constructor(private readonly adoptionService: AdoptionService) {}

  @Get()
  findAll() {
    return this.adoptionService.findAll();
  }

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

  @Post()
  create(@Body() createApplicationDto: CreateApplicationDto) {
    return this.adoptionService.create(createApplicationDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateApplicationDto: UpdateApplicationDto) {
    return this.adoptionService.update(+id, updateApplicationDto);
  }
}
Enter fullscreen mode Exit fullscreen mode

2.2 adoption.module.ts

Declares the Adoption Module and imports necessary services.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AdoptionService } from './adoption.service';
import { AdoptionController } from './adoption.controller';
import { Application } from './application.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Application])],
  controllers: [AdoptionController],
  providers: [AdoptionService],
})
export class AdoptionModule {}
Enter fullscreen mode Exit fullscreen mode

2.3 adoption.service.ts

Handles the business logic for adoption processes.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Application } from './application.entity';
import { CreateApplicationDto } from './dto/create-application.dto';
import { UpdateApplicationDto } from './dto/update-application.dto';

@Injectable()
export class AdoptionService {
  constructor(
    @InjectRepository(Application)
    private applicationsRepository: Repository<Application>,
  ) {}

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

  findOne(id: number): Promise<Application> {
    return this.applicationsRepository.findOne(id);
  }

  create(createApplicationDto: CreateApplicationDto): Promise<Application> {
    const application = this.applicationsRepository.create(createApplicationDto);
    return this.applicationsRepository.save(application);
  }

  async update(id: number, updateApplicationDto: UpdateApplicationDto): Promise<Application> {
    await this.applicationsRepository.update(id, updateApplicationDto);
    return this.findOne(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

2.4 application.entity.ts

Defines the Application Entity for adoption applications.

import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Pet } from '../pets/pet.entity';
import { User } from '../users/user.entity';

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

  @ManyToOne(() => Pet)
  pet: Pet;

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

  @Column()
  message: string;

  @Column({ default: 'pending' })
  status: string;
}
Enter fullscreen mode Exit fullscreen mode

2.5 dto/ (Adoption-Related DTOs)

  • create-application.dto.ts
export class CreateApplicationDto {
  readonly petId: number;
  readonly userId: number;
  readonly message: string;
}
Enter fullscreen mode Exit fullscreen mode
  • update-application.dto.ts
export class UpdateApplicationDto {
  readonly status?: string;
}
Enter fullscreen mode Exit fullscreen mode

Summary

  • Pets Management Module (pets/): Handles all

operations related to pets (listing, creating, updating, deleting).

  • Controller: Defines routes for managing pets.
  • Service: Handles business logic for managing pets.
  • Entity: Represents the pet in the database.
  • DTOs: For validation of data (create/update pet).

    • Adoption Management Module (adoption/): Manages the submission and processing of adoption applications.
  • Controller: Defines routes for submitting and managing applications.

  • Service: Contains business logic for processing adoption applications.

  • Entity: Represents an adoption application in the database.

  • DTOs: For validation of data (create/update application).

These modules provide a structured and scalable approach to managing pets and adoption applications in a NestJS application. You can further extend these modules as needed for your Pet Adoption Platform.

Here’s the full code for the notifications module (handling email, SMS, and push notifications), the admin module (admin-specific functionality for managing pets and adoption applications), and the main setup of the application including the root module (app.module.ts) and the entry point (main.ts).

Backend Folder Structure

├── src/
│   ├── modules/
│   │   ├── notifications/       # Notification handling (email, SMS, push notifications)
│   │   │   ├── notifications.module.ts  # Notifications module declaration
│   │   │   ├── notifications.service.ts # Notification logic (for emails, push notifications)
│   │   │   ├── email.service.ts         # Email sending service
│   │   │   └── push.service.ts          # Push notification logic (Firebase integration)
│   │   ├── admin/               # Admin-specific functionality
│   │   │   ├── admin.controller.ts  # Routes for admin operations (approving adoptions, managing pets)
│   │   │   ├── admin.module.ts      # Admin module declaration
│   │   │   └── admin.service.ts     # Admin-specific business logic
│   ├── app.module.ts            # Root application module
│   ├── main.ts                  # Entry point of the application
Enter fullscreen mode Exit fullscreen mode

1. Notifications Module (notifications/)

This module handles notification logic, including sending emails and push notifications (e.g., using Firebase).

1.1 notifications.module.ts

Declares the Notifications Module, importing the necessary services for sending email and push notifications.

import { Module } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import { EmailService } from './email.service';
import { PushService } from './push.service';

@Module({
  providers: [NotificationsService, EmailService, PushService],
  exports: [NotificationsService], // Exporting so other modules can use it
})
export class NotificationsModule {}
Enter fullscreen mode Exit fullscreen mode

1.2 notifications.service.ts

Handles the logic for sending notifications, whether by email or push.

import { Injectable } from '@nestjs/common';
import { EmailService } from './email.service';
import { PushService } from './push.service';

@Injectable()
export class NotificationsService {
  constructor(
    private emailService: EmailService,
    private pushService: PushService,
  ) {}

  // Method to send email notifications
  async sendEmailNotification(to: string, subject: string, message: string): Promise<void> {
    await this.emailService.sendEmail(to, subject, message);
  }

  // Method to send push notifications
  async sendPushNotification(token: string, message: string): Promise<void> {
    await this.pushService.sendPushNotification(token, message);
  }
}
Enter fullscreen mode Exit fullscreen mode

1.3 email.service.ts

This service handles sending emails (e.g., using SendGrid, Nodemailer, or other email services).

import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';

@Injectable()
export class EmailService {
  private transporter = nodemailer.createTransport({
    service: 'gmail', // Use your email service (Gmail, SendGrid, etc.)
    auth: {
      user: process.env.EMAIL_USER, // From environment variables
      pass: process.env.EMAIL_PASS, // From environment variables
    },
  });

  async sendEmail(to: string, subject: string, message: string): Promise<void> {
    const mailOptions = {
      from: process.env.EMAIL_USER,
      to,
      subject,
      text: message,
    };

    await this.transporter.sendMail(mailOptions);
  }
}
Enter fullscreen mode Exit fullscreen mode

1.4 push.service.ts

This service handles sending push notifications (e.g., using Firebase Cloud Messaging).

import { Injectable } from '@nestjs/common';
import * as admin from 'firebase-admin'; // Firebase Admin SDK for push notifications

@Injectable()
export class PushService {
  constructor() {
    const serviceAccount = require(process.env.FIREBASE_CREDENTIALS_PATH);
    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
    });
  }

  async sendPushNotification(token: string, message: string): Promise<void> {
    const payload = {
      notification: {
        title: 'Adoption Update',
        body: message,
      },
    };

    await admin.messaging().sendToDevice(token, payload);
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Admin Module (admin/)

This module contains the admin-specific functionality, allowing admins to manage pets and adoption applications.

2.1 admin.controller.ts

Defines routes for the admin to manage pets and approve or reject adoption applications.

import { Controller, Get, Put, Param, Body } from '@nestjs/common';
import { AdminService } from './admin.service';

@Controller('admin')
export class AdminController {
  constructor(private readonly adminService: AdminService) {}

  // Fetch all adoption applications
  @Get('applications')
  getAllApplications() {
    return this.adminService.findAllApplications();
  }

  // Approve or reject an application
  @Put('applications/:id')
  updateApplicationStatus(
    @Param('id') id: string,
    @Body('status') status: string,
  ) {
    return this.adminService.updateApplicationStatus(+id, status);
  }

  // Fetch all pets
  @Get('pets')
  getAllPets() {
    return this.adminService.findAllPets();
  }
}
Enter fullscreen mode Exit fullscreen mode

2.2 admin.module.ts

Declares the Admin Module and imports the necessary services.

import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { PetsModule } from '../pets/pets.module';
import { AdoptionModule } from '../adoption/adoption.module';

@Module({
  imports: [PetsModule, AdoptionModule], // Admin can manage both pets and applications
  controllers: [AdminController],
  providers: [AdminService],
})
export class AdminModule {}
Enter fullscreen mode Exit fullscreen mode

2.3 admin.service.ts

Contains the business logic for handling admin-specific operations such as approving adoption applications or managing pets.

import { Injectable } from '@nestjs/common';
import { PetsService } from '../pets/pets.service';
import { AdoptionService } from '../adoption/adoption.service';

@Injectable()
export class AdminService {
  constructor(
    private petsService: PetsService,
    private adoptionService: AdoptionService,
  ) {}

  // Fetch all adoption applications
  async findAllApplications() {
    return this.adoptionService.findAll();
  }

  // Update application status
  async updateApplicationStatus(id: number, status: string) {
    return this.adoptionService.update(id, { status });
  }

  // Fetch all pets
  async findAllPets() {
    return this.petsService.findAll();
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Root Application Setup

3.1 app.module.ts

The root application module that imports all feature modules like AdminModule, NotificationsModule, PetsModule, AdoptionModule, etc.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PetsModule } from './modules/pets/pets.module';
import { AdoptionModule } from './modules/adoption/adoption.module';
import { NotificationsModule } from './modules/notifications/notifications.module';
import { AdminModule } from './modules/admin/admin.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }), // Load environment variables globally
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DATABASE_HOST,
      port: parseInt(process.env.DATABASE_PORT, 10),
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      autoLoadEntities: true,
      synchronize: true, // Set to `false` in production
    }),
    PetsModule,
    AdoptionModule,
    NotificationsModule,
    AdminModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

3.2 main.ts

The entry point of the NestJS application, where the application is bootstrapped.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable CORS
  app.enableCors();

  // Use a global validation pipe to validate incoming data
  app.useGlobalPipes(new ValidationPipe());

  await app.listen(process.env.PORT || 3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

Summary

  • Notifications Module (notifications/):

    • NotificationsService: Manages sending both email and push notifications.
    • EmailService: Uses Nodemailer or other services (e.g., Gmail, SendGrid) to send emails.
    • PushService: Sends push notifications via Firebase Cloud Messaging.
  • Admin Module (admin/):

    • AdminController: Handles routes for approving or rejecting adoption applications and managing pets.
    • AdminService: Contains business logic for admin-specific tasks.
  • AppModule (app.module.ts): The root module of the application that imports other feature modules like AdminModule,

NotificationsModule, and PetsModule.

  • Main.ts: The entry point of the NestJS application, where the app is initialized and listens on a specific port.

This structure ensures scalability, modularity, and separation of concerns across different functionalities of the Pet Adoption Platform, with features like notification handling, admin-specific operations, and an organized root application setup.

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.

💖 💪 🙅 🚩
nadim_ch0wdhury
Nadim Chowdhury

Posted on October 10, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related