Skip to content

This is a React-based Lawyer Appointment Booking Web App that allows users to browse lawyer profiles and schedule appointments.

Notifications You must be signed in to change notification settings

muhammad-tamim/web-project-25

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Project Name: Law.BD

Project Description:

This is a React-based Lawyer Appointment Booking Web App that allows users to browse lawyer profiles and schedule appointments. The application includes multiple pages, data persistence, interactive UI elements, routing, and visual representation using charts.

web-project-25.1.mp4

Live Site Link:

https://web-project-25.netlify.app/

What I learned new while building this project:

  1. How to avoid repeating tailwind classes for NavLinks isActive:

form this:

<div>
    {navItems.map((item) => <NavLink key={item.id} to={item.to} 
    className={({ isActive }) => isActive ? "text-primary-content/70 font-medium text-lg border-b-2 border-primary ""text-primary-content/70 font-medium text-lg"}>{item.label}</NavLink>)}
</div>

to this:

<div>
{navItems.map((item) => <NavLink key={item.id} to={item.to}
    className={({ isActive }) => {
        const baseClasses = "text-primary-content/70 font-medium text-lg";
        const activeClasses = "border-b-2 border-primary";
        return isActive ? `${baseClasses} ${activeClasses}` : baseClasses;
    }}>{item.label}</NavLink>)}
</div>
  1. How to add counting animation:
npm i react-countup
npm i react-intersection-observer
import React from 'react';
import lawyers from '../../../assets/images/success-doctor.png'
import review from '../../../assets/images/success-review.png'
import initiated from '../../../assets/images/success-patients.png'
import staffs from '../../../assets/images/success-staffs.png'
import CountUp from 'react-countup';
import { useInView } from 'react-intersection-observer';

const SuccessNumbers = () => {
    const { ref, inView } = useInView({ threshold: .5, triggerOnce: true }); // percentage of element visible before triggering (0 - 1)

    const successItems = [
        { id: 1, image: `${lawyers}`, number: 199, label: "Total Lawyers" },
        { id: 2, image: `${review}`, number: 467, label: "Total Reviews" },
        { id: 3, image: `${initiated}`, number: 1900, label: "Total Initiated" },
        { id: 4, image: `${staffs}`, number: 300, label: "Total Stuffs" },
    ]


    return (
        <div ref={ref} className='py-[100px]'>
            <div className='text-center pb-8'>
                <h1 className='font-extrabold text-[40px]'>We Provide Best Law Services</h1>
                <p>Our platform connects you with verified, experienced Lawyers across various specialities — all at your convenience. </p>
            </div>
            <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6 justify-between'>
                {successItems.map((item) => <div key={item.id} className='flex flex-col items-center md:items-start md:pl-12 md:pr-[110px] py-10 gap-4 bg-primary-content/5 rounded-2xl'>
                    <img src={item.image} className='size-16' alt="" />
                    <h3 className='font-extrabold text-5xl'>{inView ? <CountUp end={item.number} duration={5} /> : 0}+</h3>
                    <p className='text-primary-content/60 font-medium text-xl'>{item.label}</p>
                </div>)}
            </div>
        </div>
    );
};

export default SuccessNumbers;
  1. Different ways to get specific date name:
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const date = new Date();
const dayName = days[date.getDay()]; // Tuesday

const date = new Date();
const longWeekday = date.toLocaleString('en-US', { weekday: 'long' }); // Tuesday
const shortWeekday = date.toLocaleString('en-US', { weekday: 'short' }); // Tue
  1. Different ways to scroll up when click show less:
  • Using useRef:
import React, { useEffect, useRef, useState } from 'react';
import LawyersCard from './LawyersCard';
import PrimaryButton from '../../../shared/components/ui/Buttons/PrimaryButton';

const Lawyers = ({ lawyers }) => {
    const [lawyersData, setLawyersData] = useState([])
    const [clickedShowButton, setClickedShowButton] = useState(false)
    const sectionRef = useRef(null);

    useEffect(() => {
        if (clickedShowButton) {
            setLawyersData(lawyers)

        }
        else {
            const showLessData = lawyers.slice(0, 6)
            setLawyersData(showLessData)
        }
    }, [clickedShowButton, lawyers])

    const handleLShowAllAndShowLess = () => {
        if (clickedShowButton) {
            // When showing less, scroll to section top
            sectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
        setClickedShowButton(!clickedShowButton)
    }
    return (
        <div ref={sectionRef}>
            <div className='text-center pb-8 space-y-4 md:space-y-8'>
                <h1 className='font-extrabold text-3xl md:text-[40px]'>Our Best Lawyers</h1>
                <p className='max-w-[1009px] mx-auto text-sm md:text-base text-primary-content/80'>Our platform connects you with verified, experienced Lawyers across various specialties — all at your convenience. Whether it's a routine checkup or urgent consultation, book appointments in minutes and receive quality care you can trust.</p>
            </div>
            <div className='grid grid-cols-1 xl:grid-cols-2 justify-between gap-10'>
                {lawyersData.map((lawyer) => <LawyersCard key={lawyer.id} lawyer={lawyer}></LawyersCard>)}
            </div>
            <div className='flex justify-center pt-8'>
                <PrimaryButton onClick={handleLShowAllAndShowLess} label={clickedShowButton ? "Show Less Lawyer" : "Show All Lawyer"}></PrimaryButton>
            </div>
        </div >
    );
};

export default Lawyers;
  • Using scrollTo: Goes to a specific scroll position from the very top of the page
const handleLShowAllAndShowLess = () => {
    if (clickedShowButton) {
        window.scrollTo({ top: 920, behavior: 'smooth' });
    }
    setClickedShowButton(!clickedShowButton)
}
  1. How to use useLocation() hook on ErrorPage to get path:
import React from 'react';
import Navbar from '../shared/components/structure/Navbar';
import { Link, useLocation } from 'react-router';

const ErrorPage = () => {
    const location = useLocation();
    return (
        <div>
            <Navbar></Navbar>
            <div className='min-h-[calc(100vh-105px)] flex flex-col items-center justify-center space-y-4'>
                <h1 className='font-bold text-5xl'>404</h1>
                <p className='text-primary-content/70 text-2xl'>No route matches URL "{location.pathname}"</p>
                <Link to={'/'}><button className='btn btn-primary rounded-full text-white btn-xl'>Home Page</button></Link>
            </div>
        </div>
    );
};

export default ErrorPage;
  1. How to implement dynamic-title:
import React, { useEffect } from 'react';
import { useLocation, useParams } from 'react-router';

const DynamicTitle = () => {
    const location = useLocation()
    const { id } = useParams()
    console.log(id)
    useEffect(() => {
        let title = 'law.bd';

        switch (location.pathname) {
            case '/':
                title = 'Home';
                break;
            case '/bookings':
                title = 'Law.BD | Bookings';
                break;
            case '/blogs':
                title = 'Law.BD | Blogs';
                break;
            case '/contact-us':
                title = 'Law.BD | Contact-Us';
                break;
            case '/contact-now':
                title = 'Law.BD | Contact-Now';
                break;
            default:
                title = '404 Not Found'
        }

        if (location.pathname.startsWith('/lawyers-details')) {
            title = `Law.bd | Lawyers-Details | ${id}`;
        }


        document.title = title;


    }, [location, id])

    return null
};

export default DynamicTitle;
import React from 'react';
import Navbar from '../../shared/components/structure/Navbar';
import { Outlet } from 'react-router';
import Footer from '../../shared/components/structure/Footer';
import Container from '../../shared/components/structure/Container';
import DynamicTitle from '../../shared/components/DynamicTitle';

const MainLayout = () => {
    return (
        <div>
            <DynamicTitle></DynamicTitle>
            <Navbar></Navbar>
            <Container>
                <Outlet></Outlet>
            </Container>
            <Footer></Footer>
        </div>
    );
};

export default MainLayout;

Challenges I faced while building this project:

  1. While Designing the lawyers card
import React from 'react';
import { AiOutlineTrademark } from 'react-icons/ai';
import { Link } from 'react-router';


const LawyersCard = ({ lawyer }) => {
    const { id, name, specialization, experience, license_number, image, availability } = lawyer || {}

    const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    const date = new Date();
    const dayName = days[date.getDay()];

    const todayAvailabilityCheck = availability.find((available) => available === dayName)

    return (
        <div className='border border-primary-content/15 rounded-2xl p-6 flex flex-col md:flex-row items-center gap-10 md:gap-16 text-center md:text-left'>
            <div className='flex-shrink-0 w-full md:w-[159px]'>
                <img src={image} className='rounded-[12px] w-full h-[158px]' alt="" />
            </div>
            <div className='flex-1 space-y-2'>
                <div className='flex gap-2'>
                    <p className={`${todayAvailabilityCheck ? "text-success bg-success/10 rounded-full px-[14px] py-[5px] font-medium text-xs" : "text-error bg-error/10 rounded-full px-[14px] py-[5px] font-medium text-xs"}`}>{todayAvailabilityCheck ? "Available" : "Not Available"}</p>
                    <p className='text-info bg-info/10 rounded-full px-[14px] py-[5px] font-medium text-xs'>{experience}+ Years Experience</p>
                </div>
                <div className=''>
                    <h2 className='font-extrabold text-2xl'>{name}</h2>
                    <p className='font-medium text-lg text-primary-content/60'>{specialization} Expert</p>
                    <p className='font-medium text-base text-primary-content/60 flex items-center gap-2'><AiOutlineTrademark size={16} />
                        License No: {license_number}</p>
                </div>
                <div>
                    <Link to={`/ lawyers - details /:${id}`}>
                        <button className='cursor-pointer rounded-full border border-info/20 w-full py-2 text-info font-bold text-base hover:text-neutral-content hover:bg-info'>View Details</button>
                    </Link>
                </div>
            </div>
        </div >
    );
};

export default LawyersCard;

here,

  • flex-shrink-0 prevents the image from shrinking when space is tight.
  • w-[180px] fixes the image column size.
  • flex-1 lets the text area take up the rest.
  1. To designing the re-chart:

images

import { BarChart, Bar, Cell, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts';

const Rechart = ({ lawyers }) => {

    const colors = [
        "#6366F1", // Indigo
        "#10B981", // Emerald
        "#F59E0B", // Amber
        "#EF4444", // Red
        "#3B82F6", // Blue
        "#8B5CF6", // Violet
        "#14B8A6", // Teal
        "#EC4899", // Pink
        "#22C55E", // Green
        "#EAB308", // Yellow
        "#0EA5E9", // Sky
        "#A855F7", // Purple
    ];

    const getPath = (x, y, width, height) => {
        return `M${x},${y + height}C${x + width / 3},${y + height} ${x + width / 2},${y + height / 3}
  ${x + width / 2}, ${y}
  C${x + width / 2},${y + height / 3} ${x + (2 * width) / 3},${y + height} ${x + width}, ${y + height}
  Z`;
    };

    const TriangleBar = (props) => {
        const { fill, x, y, width, height } = props;

        return <path d={getPath(x, y, width, height)} stroke="none" fill={fill} />;
    };

    return (
        <div className='flex justify-center rounded-2xl border border-primary-content/15 mt-8 p-2 md:p-8'>
            <BarChart
                style={{ width: '100%', maxHeight: '60vh', aspectRatio: 1.618 }}
                responsive
                data={lawyers}
            >
                <CartesianGrid vertical={false} horizontal strokeDasharray="3 3" />
                <XAxis dataKey="name"
                    axisLine={false}
                    tickLine={false}
                    // interval={0}
                    style={{ fontSize: '12px' }}
                    tickFormatter={(name) => {
                        const parts = name.split(" "); // split full name into parts
                        return parts[parts.length - 1]; // return last part (last name)
                    }} />
                <YAxis width="auto" axisLine={false} tickLine={false} />
                <Bar dataKey="price_bdt" fill="#8884d8" shape={TriangleBar} label={{ position: 'top' }}>
                    {lawyers.map((_entry, index) => (
                        <Cell key={`cell-${index}`} fill={colors[index % 20]} />
                    ))}
                </Bar>
            </BarChart>
        </div>
    );
};

export default Rechart;

Contact With Me:

[email protected] | +8801586090360 (WhatsApp)

https://www.linkedin.com/in/tamim-muhammad

About

This is a React-based Lawyer Appointment Booking Web App that allows users to browse lawyer profiles and schedule appointments.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published