DEV Community

pirvanm
pirvanm

Posted on

How do you make a FullStack [Focus on React] Demo App for Interviews using Laravel + React with Dockerize Part 5

Previously, I started react js side ,Part 4- How do you make a FullStack Demo App for Interviews using Laravel + React with Dockerize

We’re continuing in the frontend/src/features/mentors/ section, and you’d like to refine and standardize styles inside MentorCard.module.css so it’s cleaner, reusable, and more consistent.

.mentor-card {
  background: white;
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  padding: 1.25rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  transition: all 0.2s ease;
}

.mentor-card:hover {
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transform: translateY(-2px);
}

.mentor-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 0.75rem;
}

.mentor-name {
  font-size: 1.1rem;
  font-weight: 600;
  color: #1e293b;
  margin-bottom: 0.25rem;
}

.mentor-title {
  font-size: 0.95rem;
  color: #475569;
  margin-bottom: 0.25rem;
}

.mentor-expertise {
  font-size: 0.85rem;
  color: #64748b;
  margin-bottom: 0.75rem;
}

.mentor-link {
  font-size: 0.9rem;
  color: #4f46e5;
  text-decoration: none;
  font-weight: 500;
}

.mentor-link:hover {
  text-decoration: underline;
}

Enter fullscreen mode Exit fullscreen mode

We’ll keep it consistent with the same folder level (frontend/src/features/mentors/) and design style you just refined for MentorCard.module.css.
Here’s a clean, reusable SkeletonMentorCard.module.css you can drop in:

.mentor-card {
  display: flex;
  flex-direction: column;
  text-align: center;
  padding: 1.5rem;
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

.mentor-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #e2e8f0;
  margin-bottom: 0.75rem;
  animation: pulse 2s infinite ease-in-out;
}

.mentor-name {
  height: 24px;
  width: 120px;
  background: #e2e8f0;
  border-radius: 0.375rem;
  margin: 0.25rem 0;
  animation: pulse 2s infinite ease-in-out;
  animation-delay: 0.2s;
}

.mentor-title {
  height: 18px;
  width: 140px;
  background: #f7fafc;
  border-radius: 0.375rem;
  margin: 0.25rem 0;
  animation: pulse 2s infinite ease-in-out;
  animation-delay: 0.4s;
}

.mentor-expertise {
  height: 16px;
  width: 110px;
  background: #f7fafc;
  border-radius: 0.375rem;
  margin: 0.75rem 0;
  animation: pulse 2s infinite ease-in-out;
  animation-delay: 0.6s;
}

.mentor-link {
  display: inline-block;
  height: 32px;
  width: 90px;
  background: #e2e8f0;
  border-radius: 0.5rem;
  margin-top: 0.5rem;
  animation: pulse 2s infinite ease-in-out;
  animation-delay: 0.8s;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.7;
  }
}
Enter fullscreen mode Exit fullscreen mode

here’s a tidy, reusable MentorCard.tsx “partial”-style component that lines up with the CSS module you’ve set up. Drop this into frontend/src/features/mentors/MentorCard.tsx.

import { Link } from 'react-router';
import styles from './MentorCard.module.css'
import type { Mentor } from '../../types/mentor';


const MentorCard: React.FC<Mentor> = ({
  id,
  title,
  fullName,
  expertise,
  avatar
}) => {
  return (
    <>
    <article className={styles['mentor-card']}>
        <img src={avatar ?? 'https://randomuser.me/api/portraits/women/45.jpg'} alt={fullName} className={styles['mentor-avatar']} />
        <h2 className={styles['mentor-name']}>{fullName}</h2>
        <div className={styles['mentor-title']}>{title}</div>
        <div className={styles['mentor-expertise']}>{expertise.slice(0, 3).join(' • ').toUpperCase()}</div>
        <Link to={`/mentor/${id}`} className={styles['mentor-link']}>View Profile</Link>
      </article>
    </>
  );
}

export default MentorCard;
Enter fullscreen mode Exit fullscreen mode

here’s a solid MentorCard.test.tsx using React Testing Library + Jest.
It covers rendering, tags, avatar vs initials fallback, card click, primary action (and that it doesn’t bubble), custom className, aria-label, and children rendering.

import { render, screen } from '@testing-library/react';
import MentorCard from './MentorCard';
import type { Mentor } from '../../types/mentor';
import { MemoryRouter } from 'react-router';

const mockMentor: Mentor = {
  id: 1,
  fullName: 'Sarah Chen',
  email: 'sarah@example.com',
  title: 'Senior Frontend Engineer',
  bio: 'Passionate about React and mentoring.',
  technicalBio: 'Passionate about React and mentoring.',
  mentoringStyle: 'Passionate about React and mentoring.',
  audience: 'Passionate about React and mentoring.',
  avatar: 'https://example.com/avatar.jpg',
  expertise: ['react', 'typescript'],
  availability: 'open',
  joined_at: 'Jun 2020',
};

describe('MentorCard', () => {
  test('renders mentor data correctly', () => {
    render(
         <MemoryRouter>
            <MentorCard {...mockMentor} />``
        </MemoryRouter>
    );

    expect(screen.getByText('Sarah Chen')).toBeInTheDocument();
    expect(screen.getByText('Senior Frontend Engineer')).toBeInTheDocument();
    expect(screen.getByText('REACT • TYPESCRIPT')).toBeInTheDocument();

    const img = screen.getByAltText('Sarah Chen');
    expect(img).toBeInTheDocument();
    expect(img).toHaveAttribute('src', mockMentor.avatar);
  });
});
Enter fullscreen mode Exit fullscreen mode

Let’s add a tiny layer of logic so your UI cleanly handles loading, success, empty, and error states, all at the same folder level: frontend/src/features/mentors/.

import { useMentors } from "../../api/queries/mentors/useMentors";
import Error from "../../shared/ui/Error/Error";
import MentorCard from "./MentorCard";
import SkeletonMentorCard from "./SkeletonMentorCard";

const MentorList: React.FC = () => {
  const { data: mentors, isLoading, error } = useMentors();

if (isLoading) {
  return (
    <main className="mentors-grid">
      <SkeletonMentorCard />
      <SkeletonMentorCard />
      <SkeletonMentorCard />
      <SkeletonMentorCard />
      <SkeletonMentorCard />
      <SkeletonMentorCard />
    </main>
  );
}

  if (error) return <Error />;
  if (!mentors || mentors.length === 0) return <p>No mentors available.</p>;

  return (
    <>
        <main className="mentors-grid">
          {mentors.map((mentor) => (
            <MentorCard key={mentor.id} {...mentor} />
          ))}
        </main>
    </>
  );
}

export default MentorList;
Enter fullscreen mode Exit fullscreen mode

let’s wrap the mentors feature folder (frontend/src/features/mentors/) with a clean index barrel so everything exports in a predictable, maintainable way. This way, consumers only import from the folder, not individual files.

import styles from './SkeletonMentorCard.module.css';

const SkeletonMentorCard: React.FC = () => {
  return (
    <article className={styles['mentor-card']}>
      <div className={styles['mentor-avatar']} />
      <div className={styles['mentor-name']} />
      <div className={styles['mentor-title']} />
      <div className={styles['mentor-expertise']} />
      <div className={styles['mentor-link']} />
    </article>
  );
};

export default SkeletonMentorCard;
Enter fullscreen mode Exit fullscreen mode

We get back 1 step and go next folder layouts so we in frontend/src/layouts
/MainLayout.tsx got :

import { Outlet } from "react-router";
import MainNavbar from "./MainNavbar";

const MainLayout: React.FC = () => {
  return (
    <>
        <MainNavbar />
        <div className="page-container">
            <Outlet />
        </div>
    </>
  );
}

export default MainLayout;

Enter fullscreen mode Exit fullscreen mode

And for navigation, we got frontend/src/layouts
/MainNavbar.tsx :

import React from 'react';
import { Link } from 'react-router';

const MainNavbar: React.FC = () => {
  return (
    <nav className="navbar">
      <div className="nav-container">
        <Link to={'/'} className="nav-logo">MentorHub</Link>
      </div>
    </nav>
  );
};

export default MainNavbar;
Enter fullscreen mode Exit fullscreen mode

And we end this part with "types" ,frontend/src/types
/mentor.ts

export type Mentor = {
  id: number;
  fullName: string;
  email: string | null;
  title: string;
  bio: string | null;
  technicalBio: string | null;
  mentoringStyle: string | null;
  audience: string | null;
  avatar: string | null;
  expertise: string[];
  availability: 'open' | 'limited' | 'full' | 'paused';
  joined_at: string;
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)