-
Notifications
You must be signed in to change notification settings - Fork 1
Create page.tsx #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Create page.tsx #20
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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', { | ||||||||||||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||||||||||||
| headers: { 'Content-Type': 'application/json' }, | ||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ | ||||||||||||||||||||||||||||||||||||||||||
| type: 'generate_jd', | ||||||||||||||||||||||||||||||||||||||||||
| content: formData | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP Error CheckThe fetch API does not reject promises on HTTP error status codes. The application proceeds to parse JSON without validating response.ok, potentially causing runtime errors or silent failures when the backend fails. Commitable Suggestion
Suggested change
Standards
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const data = await response.json(); | ||||||||||||||||||||||||||||||||||||||||||
| setResult(data.generatedJD); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| console.error('Error:', error); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| setLoading(false); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The API call and error handling logic can be made more robust:
|
||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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"> | ||||||||||||||||||||||||||||||||||||||||||
| <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} | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incomplete Button ValidationThe submit button's disabled state logic omits the experience field, which is marked as required in the form schema. This creates a logical inconsistency where the button becomes enabled before all mandatory fields are populated. Standards
|
||||||||||||||||||||||||||||||||||||||||||
| 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 }} /> | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using I've suggested replacing it with a
Comment on lines
+111
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The component renders the API response string directly via Severity Level: Critical 🚨
Suggested change
Why it matters? ⭐The PR currently injects the API response into the DOM via dangerouslySetInnerHTML with no sanitization. If the backend or the model ever returns HTML containing scripts or attacker-controlled markup, this enables XSS in clients. Replacing the HTML injection with rendering the string as plain text (or sanitizing it) prevents script execution and fixes a real security vulnerability. The suggested change to render {result} as text (and use CSS like whitespace-pre-wrap) is a safe, straightforward mitigation. Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** app/generate/page.tsx
**Line:** 111:112
**Comment:**
*Security: The component renders the API response string directly via `dangerouslySetInnerHTML` without any sanitization, so if the backend ever includes user-controlled or model-generated HTML (which can contain `<script>` tags), this will allow arbitrary JavaScript execution in the user's browser (XSS).
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. XSS via dangerouslySetInnerHTMLServer response rendered directly as HTML without sanitization enables stored XSS attacks. Malicious job descriptions from backend could inject JavaScript executing in user browsers, leading to session hijacking and data theft. Commitable Suggestion
Suggested change
Standards
|
||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing CSRF Protection
Form submission lacks CSRF token protection allowing cross-site request forgery attacks. Attackers could trick authenticated users into generating malicious job descriptions through crafted websites.
Standards