Skip to content

Add "Hire CJ" feature to WheresCJ component with professional service… - #102

Merged
cj-vana merged 2 commits into
betafrom
new-pr
Sep 25, 2025
Merged

cj-vana merged 2 commits into
betafrom
new-pr

Conversation

@cj-vana

@cj-vana cj-vana commented Sep 25, 2025 •

Copy link
Copy Markdown
Collaborator

User description

  • Created HireCJModal component
  • Added "Hire CJ for Your Event" button to WheresCJ popover (appears both when online and offline)

PR Type

Enhancement


Description

  • Added professional services modal for hiring CJ

  • Integrated "Hire CJ" button in WheresCJ popover

  • Enhanced user interaction with improved hover handling

  • Created comprehensive service showcase with contact information


Diagram Walkthrough

flowchart LR
  A["WheresCJ Component"] --> B["Hire CJ Button"]
  B --> C["HireCJModal"]
  C --> D["Contact Information"]
  C --> E["Skills & Services"]
  C --> F["Availability Details"]
Loading

File Walkthrough

Relevant files
Enhancement
HireCJModal.tsx
New professional services modal component                               

apps/web/src/components/HireCJModal.tsx

  • Created new modal component for professional services
  • Added comprehensive skills showcase with icons
  • Included contact information and booking details
  • Implemented responsive design with backdrop blur
+146/-0 
WheresCJ.tsx
Enhanced WheresCJ with hire functionality                               

apps/web/src/components/WheresCJ.tsx

  • Added "Hire CJ" button to both online/offline states
  • Integrated HireCJModal component with state management
  • Enhanced hover handling with timeout delays
  • Improved popover layout and user interaction
+70/-22 

…s modal

 - Created HireCJModal component
  - Added "Hire CJ for Your Event" button to WheresCJ popover (appears both when online and offline)
@netlify

netlify Bot commented Sep 25, 2025 •

Copy link
Copy Markdown

✅ Deploy Preview for sounddocsbeta ready!

Name Link
🔨 Latest commit 1417e82
🔍 Latest deploy log https://app.netlify.com/projects/sounddocsbeta/deploys/68d585994028ba000823449c
😎 Deploy Preview https://deploy-preview-102--sounddocsbeta.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Type Issue

Using NodeJS.Timeout in a browser React component can cause type incompatibilities depending on the TS config; consider using ReturnType for broader compatibility.

const [showHireModal, setShowHireModal] = useState(false);
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout | null>(null);
Focus Trap/Accessibility

The hover-based popover contains interactive content (a button) but lacks keyboard accessibility and focus management; ensure the "Hire CJ" action is reachable via keyboard and that the popover/modal is announced properly.

return (
  <>
    <div
      className="relative flex items-center"
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <div className="p-2 rounded-full bg-gray-800/50 hover:bg-gray-700/80 text-indigo-400 hover:text-indigo-300 transition-all duration-200 cursor-pointer">
        <MapPin className="h-5 w-5" />
      </div>

      {isHovered && (
        <div
          className="absolute bottom-full right-0 mb-3 w-72 bg-gray-950/90 backdrop-blur-lg border border-gray-700 rounded-lg shadow-2xl shadow-indigo-900/20 z-50"
          style={{ animation: "fadeInUp 0.3s ease-out forwards" }}
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
        >
          <PopoverContent />
        </div>
      )}
Modal Accessibility

Modal lacks ARIA roles, focus trap, and Escape key handling; add role="dialog", aria-modal, initial focus, and close on Escape to improve accessibility.

<div
  className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
  onClick={onClose}
>
  <div
    className="bg-gray-950/95 backdrop-blur-lg border border-gray-700 rounded-lg shadow-2xl shadow-indigo-900/20 max-w-2xl w-full max-h-[90vh] overflow-y-auto"
    onClick={(e) => e.stopPropagation()}
  >
    <div className="p-6">
      <div className="flex items-center justify-between mb-6">
        <h2 className="text-2xl font-bold text-white">
          Hire CJ - Professional Audio Engineer
        </h2>
        <button
          onClick={onClose}
          className="p-2 rounded-full hover:bg-gray-800 transition-colors"
        >
          <X className="h-5 w-5 text-gray-400" />
        </button>
      </div>

      <div className="space-y-6">
        <div className="bg-indigo-900/30 border border-indigo-700/50 rounded-lg p-5">
          <h3 className="text-lg font-semibold text-indigo-300 mb-3 flex items-center">
            <Mail className="h-5 w-5 mr-2" />
            Get In Touch
          </h3>
          <p className="text-white mb-2">
            Ready to elevate your event's audio experience? Contact me at:
          </p>
          <a
            href="mailto:cj@sounddocs.org"
            className="text-indigo-400 hover:text-indigo-300 font-semibold text-lg transition-colors"
          >
            cj@sounddocs.org
          </a>
          <p className="text-gray-300 mt-3">Please include:</p>
          <ul className="list-disc list-inside text-gray-300 mt-2 space-y-1">
            <li>Event dates and location</li>
            <li>Type of event (concert, corporate, festival, theater, etc.)</li>
            <li>Specific role needed (FOH, Monitors, System Tech, RF, etc.)</li>
            <li>Venue size and expected attendance</li>
            <li>Equipment details if available</li>
          </ul>
        </div>

        <div>
          <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
            <Briefcase className="h-5 w-5 mr-2 text-indigo-400" />
            Skills & Services
          </h3>
          <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
            {skills.map((skill, index) => {
              const Icon = skill.icon;
              return (
                <div
                  key={index}
                  className="flex items-start p-3 bg-gray-800/50 rounded-lg hover:bg-gray-800/70 transition-colors"
                >
                  <Icon className="h-5 w-5 text-indigo-400 mr-3 mt-0.5 flex-shrink-0" />
                  <span className="text-gray-200 text-sm">{skill.text}</span>
                </div>
              );
            })}
          </div>
        </div>

        <div className="bg-gray-800/50 rounded-lg p-5">
          <h3 className="text-lg font-semibold text-white mb-3 flex items-center">
            <MapPin className="h-5 w-5 mr-2 text-indigo-400" />
            Availability
          </h3>
          <p className="text-gray-300">
            Available for events worldwide. Based in the United States with extensive touring
            experience. Passport ready for international events.
          </p>
        </div>

        <div className="bg-gray-800/50 rounded-lg p-5">
          <h3 className="text-lg font-semibold text-white mb-3 flex items-center">
            <Calendar className="h-5 w-5 mr-2 text-indigo-400" />
            Booking Information
          </h3>
          <p className="text-gray-300">
            For the best chance of securing your dates, please reach out as early as possible.
            I work with a production company full time, and my schedule fills up quickly
          </p>
        </div>
      </div>

      <div className="mt-6 flex justify-end">
        <button
          onClick={onClose}
          className="px-6 py-2 bg-gray-800 hover:bg-gray-700 text-white rounded-lg transition-colors"
        >
          Close
        </button>
      </div>
    </div>
  </div>
</div>

@qodo-code-review

qodo-code-review Bot commented Sep 25, 2025 •

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Enable closing modal with Escape key

Improve accessibility by adding a useEffect hook to the HireCJModal component
that allows closing the modal with the 'Escape' key.

apps/web/src/components/HireCJModal.tsx [21-37]

+import React, { useEffect } from "react";
+import {
+  X,
+  Mail,
+...
+
 const HireCJModal: React.FC<HireCJModalProps> = ({ isOpen, onClose }) => {
+  useEffect(() => {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (event.key === "Escape") {
+        onClose();
+      }
+    };
+
+    if (isOpen) {
+      document.addEventListener("keydown", handleKeyDown);
+    }
+
+    return () => {
+      document.removeEventListener("keydown", handleKeyDown);
+    };
+  }, [isOpen, onClose]);
+
   if (!isOpen) return null;
 
   const skills = [
     { icon: Mic, text: "FOH & Monitors A1 - Front of House & Monitor Mix Engineering" },
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an accessibility issue and provides a valid solution to allow closing the modal with the 'Escape' key, which is a standard and expected behavior for modals.

Medium
Use a stable key for list rendering

Replace the index with the unique skill.text as the key prop when mapping over
the skills array to follow React best practices.

apps/web/src/components/HireCJModal.tsx [92-105]

 <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
-  {skills.map((skill, index) => {
+  {skills.map((skill) => {
     const Icon = skill.icon;
     return (
       <div
-        key={index}
+        key={skill.text}
         className="flex items-start p-3 bg-gray-800/50 rounded-lg hover:bg-gray-800/70 transition-colors"
       >
         <Icon className="h-5 w-5 text-indigo-400 mr-3 mt-0.5 flex-shrink-0" />
         <span className="text-gray-200 text-sm">{skill.text}</span>
       </div>
     );
   })}
 </div>
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out the use of an index as a key and proposes using skill.text instead. This is a good practice for performance and state consistency, although the impact is minor here as the list is static.

Low
High-level
Separate hardcoded content from components

The HireCJModal component hardcodes content like skills and contact information.
This content should be moved to an external data source, like a JSON file, and
loaded dynamically to improve maintainability.

Examples:

apps/web/src/components/HireCJModal.tsx [24-37]
  const skills = [
    { icon: Mic, text: "FOH & Monitors A1 - Front of House & Monitor Mix Engineering" },
    { icon: Volume2, text: "A2 & Stage Support - Audio Assistant, Patch, RF Coordination" },
    {
      icon: Settings,
      text: "System Design & Deployment - PA Architecture, Setup, Tuning & Optimization",
    },
    { icon: Radio, text: "RF & Intercom Systems - Wireless Coordination, Clear-Com, RTS" },
    { icon: Cable, text: "Festival & Tour Production - Signal Flow, System Integration" },
    {

 ... (clipped 4 lines)
apps/web/src/components/HireCJModal.tsx [68-84]
                <p className="text-white mb-2">
                  Ready to elevate your event's audio experience? Contact me at:
                </p>
                <a
                  href="mailto:cj@sounddocs.org"
                  className="text-indigo-400 hover:text-indigo-300 font-semibold text-lg transition-colors"
                >
                  cj@sounddocs.org
                </a>
                <p className="text-gray-300 mt-3">Please include:</p>

 ... (clipped 7 lines)

Solution Walkthrough:

Before:

// apps/web/src/components/HireCJModal.tsx
const HireCJModal: React.FC<HireCJModalProps> = ({ isOpen, onClose }) => {
  if (!isOpen) return null;

  const skills = [
    { icon: Mic, text: "FOH & Monitors A1..." },
    { icon: Volume2, text: "A2 & Stage Support..." },
    // ... more hardcoded skills
  ];

  return (
    // ... JSX with hardcoded text
    <h2>Hire CJ - Professional Audio Engineer</h2>
    <a href="mailto:cj@sounddocs.org">cj@sounddocs.org</a>
    // ...
  );
};

After:

/*
// content/hire-cj.json
{
  "title": "Hire CJ - Professional Audio Engineer",
  "email": "cj@sounddocs.org",
  "skills": [ { "icon": "Mic", "text": "FOH & Monitors A1..." } ],
  ...
}
*/

// apps/web/src/components/HireCJModal.tsx
// Assume content is fetched and passed as a prop
const HireCJModal: React.FC<HireCJModalProps> = ({ isOpen, onClose, content }) => {
  if (!isOpen) return null;

  return (
    // ... JSX using dynamic content
    <h2>{content.title}</h2>
    <a href={`mailto:${content.email}`}>{content.email}</a>
    // ...
  );
};
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the new HireCJModal component has a lot of hardcoded content and proposes a valid architectural improvement for maintainability, though it's not a critical bug.

Low
  • Update

@cj-vana
cj-vana merged commit 5164af7 into beta Sep 25, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant