Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions app/generate/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// app/(chat)/generate/page.tsx
'use client';
import { useState } from 'react';

export default function GenerateJD() {
const [formData, setFormData] = useState({
title: '',
industry: '',
experience: '',
details: ''
});
const [result, setResult] = useState('');
const [loading, setLoading] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);

try {
const response = await fetch('/api/chat', {
Comment on lines +5 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Component Responsibility Overload

Component handles multiple responsibilities: form state management, API interaction, and UI rendering. This violates Single Responsibility Principle and makes the component harder to maintain as features are added.

Suggested change
export default function GenerateJD() {
const [formData, setFormData] = useState({
title: '',
industry: '',
experience: '',
details: ''
});
const [result, setResult] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/chat', {
// hooks/useJobDescriptionForm.ts
import { useState } from 'react';
export function useJobDescriptionForm() {
const [formData, setFormData] = useState({
title: '',
industry: '',
experience: '',
details: ''
});
const [result, setResult] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const updateField = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return { formData, updateField, result, setResult, loading, setLoading, error, setError };
}
// services/jobDescriptionService.ts
export async function generateJobDescription(formData) {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
return data.generatedJD;
}
Standards
  • SOLID-SRP
  • Clean-Code-Component-Organization

method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
});
Comment on lines +20 to +27

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The payload sent to /api/chat does not seem to match the structure expected by the API route handler in app/(chat)/api/chat/route.ts. The API expects an object with id, messages, and modelId, but this component sends { type: 'generate_jd', content: formData }. This mismatch will likely cause the API call to fail with an error. Please ensure the client and server are aligned on the API contract.


const data = await response.json();
setResult(data.generatedJD);
Comment on lines +29 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing API Response Error Handling

Code doesn't check if response is successful before parsing JSON and accessing data.generatedJD. Non-successful API responses could cause runtime errors when accessing properties on undefined data, potentially crashing the UI.

Suggested change
const data = await response.json();
setResult(data.generatedJD);
const data = await response.json();
if (!response.ok) {
console.error('API error:', data);
setResult('Error generating job description. Please try again later.');
return;
}
setResult(data.generatedJD || 'No content generated');
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

} catch (error) {
console.error('Error:', error);
}
Comment on lines +31 to +33

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current error handling only catches network errors or errors during JSON parsing. It doesn't handle non-2xx HTTP status codes from the API (e.g., 404, 500), as fetch does not reject on these. You should check response.ok and throw an error if it's false to catch these cases. Additionally, consider adding a user-facing error message state instead of just logging to the console, to improve user experience.

Comment on lines +31 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error State Not Communicated to User

Caught errors are only logged to console without UI feedback. Users have no visibility into failure states, creating confusion when API calls fail silently.

Suggested change
} catch (error) {
console.error('Error:', error);
}
} catch (error) {
console.error('Error:', error);
setResult('<p class="text-red-500">An error occurred while generating the job description. Please try again later.</p>');
}
Standards
  • ISO-IEC-25010-Reliability-Recoverability
  • ISO-IEC-25010-Functional-Appropriateness


setLoading(false);
Comment on lines +33 to +35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To prevent the UI from getting stuck in a loading state if an error occurs (e.g., if response.json() fails), the setLoading(false) call should be placed inside a finally block. This ensures it is executed regardless of whether the try block completes successfully or throws an error.

    } finally {
      setLoading(false);
    }

};
Comment on lines +15 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden fetch error handling and ensure loading state resets via finally.

Currently, non-2xx responses will be treated as success and response.json() may throw. Use response.ok and a finally for setLoading(false).

Apply this diff:

   const handleSubmit = async (e: React.FormEvent) => {
     e.preventDefault();
-    setLoading(true);
-    
-    try {
-      const response = await fetch('/api/chat', {
+    setLoading(true);
+    try {
+      const response = await fetch('/api/chat', {
         method: 'POST',
         headers: { 'Content-Type': 'application/json' },
         body: JSON.stringify({
           type: 'generate_jd',
           content: formData
         })
       });
-      
-      const data = await response.json();
-      setResult(data.generatedJD);
-    } catch (error) {
-      console.error('Error:', error);
-    }
-    
-    setLoading(false);
+      if (!response.ok) {
+        const text = await response.text().catch(() => '');
+        throw new Error(`Failed to generate JD (${response.status} ${response.statusText})${text ? `: ${text}` : ''}`);
+      }
+      const data: { generatedJD?: string } = await response.json();
+      setResult(data.generatedJD ?? '');
+    } catch (error) {
+      console.error('Error generating JD:', error);
+      setResult('');
+    } finally {
+      setLoading(false);
+    }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
});
const data = await response.json();
setResult(data.generatedJD);
} catch (error) {
console.error('Error:', error);
}
setLoading(false);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'generate_jd',
content: formData
})
});
if (!response.ok) {
const text = await response.text().catch(() => '');
throw new Error(
`Failed to generate JD (${response.status} ${response.statusText})` +
(text ? `: ${text}` : '')
);
}
const data: { generatedJD?: string } = await response.json();
setResult(data.generatedJD ?? '');
} catch (error) {
console.error('Error generating JD:', error);
setResult('');
} finally {
setLoading(false);
}
};
🤖 Prompt for AI Agents
In app/generate/page.tsx around lines 15 to 36, the submit handler treats
non-2xx fetch responses as success and may leave loading true if an exception
occurs; update the code to check response.ok after fetch and throw an error
(including status and statusText) when not ok, only call await response.json()
on successful responses, and move setLoading(false) into a finally block so
loading is always reset; keep the existing try/catch for logging the error.


return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Generate Job Description</h1>

<form onSubmit={handleSubmit} className="space-y-6">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better accessibility, it's recommended to explicitly associate <label> elements with their form controls using the htmlFor attribute, which should match the id of the control. This helps screen reader users understand which label belongs to which input. This should be applied to all form fields: Job Title, Industry, Experience Level, and Job Details.

For example:

<div>
  <label htmlFor="job-title" className="block font-medium mb-1">Job Title</label>
  <input 
    id="job-title"
    type="text"
    ...
  />
</div>

<div>
<label className="block font-medium mb-1">Job Title</label>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
className="w-full p-2 border rounded"
placeholder="e.g., Senior Software Engineer"
required
/>
</div>

<div>
<label className="block font-medium mb-1">Industry</label>
<select
value={formData.industry}
onChange={(e) => setFormData(prev => ({ ...prev, industry: e.target.value }))}
className="w-full p-2 border rounded"
required
>
<option value="">Select Industry</option>
<option value="Technology">Technology</option>
<option value="Finance">Finance</option>
<option value="Healthcare">Healthcare</option>
<option value="Manufacturing">Manufacturing</option>
<option value="Retail">Retail</option>
</select>
</div>

<div>
<label className="block font-medium mb-1">Experience Level</label>
<select
value={formData.experience}
onChange={(e) => setFormData(prev => ({ ...prev, experience: e.target.value }))}
className="w-full p-2 border rounded"
required
>
<option value="">Select Experience Level</option>
<option value="Entry Level">Entry Level (0-2 years)</option>
<option value="Mid Level">Mid Level (3-5 years)</option>
<option value="Senior Level">Senior Level (5+ years)</option>
<option value="Lead">Lead (7+ years)</option>
</select>
</div>

<div>
<label className="block font-medium mb-1">Job Details</label>
<textarea
value={formData.details}
onChange={(e) => setFormData(prev => ({ ...prev, details: e.target.value }))}
className="w-full h-32 p-2 border rounded"
placeholder="Describe the role, responsibilities, and key requirements..."
required
/>
</div>

<button
type="submit"
disabled={loading || !formData.title || !formData.industry || !formData.details}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The disabled logic for the submit button is missing a check for formData.experience. Since the 'Experience Level' field is also marked as required, it should be included in this check to ensure the button is only enabled when all required fields are filled.

          disabled={loading || !formData.title || !formData.industry || !formData.experience || !formData.details}

className="w-full py-2 bg-purple-600 text-white rounded hover:bg-purple-700 disabled:bg-purple-300"
>
{loading ? 'Generating...' : 'Generate JD'}
</button>
</form>

{result && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2>
<div className="prose max-w-none">
<div dangerouslySetInnerHTML={{ __html: result }} />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using dangerouslySetInnerHTML with content that could be influenced by user input (even indirectly via an AI model) creates a significant Cross-Site Scripting (XSS) vulnerability. An attacker could potentially craft inputs that cause the AI to generate malicious HTML, including <script> tags, which would then be executed in the user's browser.

To mitigate this, you must sanitize the HTML string before rendering it. A popular library for this is DOMPurify.

Example usage:

import DOMPurify from 'dompurify';

// ... inside your component
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XSS Vulnerability in HTML Rendering

Unsanitized HTML content is rendered directly using dangerouslySetInnerHTML. API responses could contain malicious scripts, enabling cross-site scripting attacks that execute in the user's browser context.

Suggested change
<div dangerouslySetInnerHTML={{ __html: result }} />
import DOMPurify from 'dompurify';
// In the return statement:
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />
Standards
  • CWE-79
  • OWASP-A03

</div>
Comment on lines +111 to +113
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Sanitize HTML before injecting to prevent XSS (use DOMPurify).

Rendering API-provided HTML via dangerouslySetInnerHTML without sanitization is an XSS vector. Sanitize the HTML before injection.

Apply this diff to sanitize the HTML at render time:

-            <div dangerouslySetInnerHTML={{ __html: result }} />
+            <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />

Add this import at the top of the file:

import DOMPurify from 'isomorphic-dompurify';

Optionally, prefer sending Markdown/plain text from the API and render with a Markdown renderer to avoid dangerouslySetInnerHTML.

To verify there are no other unsafe injections in the repo, run:


🏁 Script executed:

#!/bin/bash
rg -n "dangerouslySetInnerHTML"

Length of output: 177


🏁 Script executed:

#!/bin/bash
rg -n -A3 -B3 "dangerouslySetInnerHTML" app/layout.tsx

Length of output: 211


🏁 Script executed:

#!/bin/bash
rg -n -A3 -B3 "THEME_COLOR_SCRIPT"

Length of output: 676


Sanitize API-provided HTML in app/generate/page.tsx; internal script injection in app/layout.tsx is safe

  • app/generate/page.tsx (line 112): Wrap the result payload in DOMPurify.sanitize(...) before injecting.
  • app/layout.tsx (line 54): THEME_COLOR_SCRIPT is a static, internally-defined constant—not user-controlled—so this use of dangerouslySetInnerHTML does not require sanitization.

Apply the following diff in app/generate/page.tsx:

--- a/app/generate/page.tsx
+++ b/app/generate/page.tsx
@@ -1,6 +1,7 @@
 import React from 'react';
+import DOMPurify from 'isomorphic-dompurify';
 // … other imports

           <div className="prose max-w-none">
-            <div dangerouslySetInnerHTML={{ __html: result }} />
+            <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result) }} />
           </div>

No changes required in app/layout.tsx; its script injection is from a trusted, hard-coded constant.

🧰 Tools
🪛 ast-grep (0.38.6)

[warning] 111-111: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
In app/generate/page.tsx around lines 111-113, the API-provided HTML is injected
directly; wrap the payload in DOMPurify.sanitize(...) before using
dangerouslySetInnerHTML to prevent script injection. Import a DOMPurify
implementation appropriate for your environment (e.g., import DOMPurify from
"isomorphic-dompurify" or "dompurify" for client-only), create a sanitizedHtml
variable = DOMPurify.sanitize(result) and pass that to __html instead of result;
leave app/layout.tsx unchanged as its script is a trusted constant.

</div>
)}
Comment on lines +108 to +115
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Loading Indicator

Loading state is set but not displayed to users. When API request is processing, users receive no visual feedback that their request is being handled, leading to potential multiple submissions and confusion.

Suggested change
{result && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2>
<div className="prose max-w-none">
<div dangerouslySetInnerHTML={{ __html: result }} />
</div>
</div>
)}
{loading && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<div className="flex justify-center">
<div className="animate-pulse text-purple-600">Generating job description...</div>
</div>
</div>
)}
{!loading && result && (
<div className="mt-6 p-4 bg-gray-50 rounded-lg">
<h2 className="text-lg font-medium mb-2">Generated Job Description:</h2>
<div className="prose max-w-none">
<div dangerouslySetInnerHTML={{ __html: result }} />
</div>
</div>
)}
Standards
  • Logic-Verification-User-Feedback
  • Business-Rule-User-Experience

</div>
);
}
Loading